 core/misc/edit-active.png                          |    8 +
 core/misc/edit.png                                 |    7 +
 core/modules/contextual/contextual.js              |   52 +++++-
 core/modules/contextual/contextual.module          |   56 +++++-
 core/modules/contextual/contextual.theme-rtl.css   |    2 +
 core/modules/contextual/contextual.theme.css       |   40 ++--
 core/modules/contextual/contextual.toolbar-rtl.css |   11 ++
 core/modules/contextual/contextual.toolbar.css     |   36 ++++
 core/modules/contextual/contextual.toolbar.js      |  132 +++++++++++++
 core/modules/edit/css/edit.css                     |   50 +----
 core/modules/edit/edit.info                        |    1 +
 core/modules/edit/edit.module                      |   54 +++---
 core/modules/edit/images/icon-edit-active.png      |    3 -
 core/modules/edit/images/icon-edit.png             |    5 -
 core/modules/edit/js/app.js                        |  197 +++-----------------
 .../editingWidgets/drupalcontenteditablewidget.js  |    8 +-
 .../edit/js/createjs/editingWidgets/formwidget.js  |   14 +-
 core/modules/edit/js/edit.js                       |   55 ++----
 core/modules/edit/js/models/edit-app-model.js      |    3 +-
 core/modules/edit/js/routers/edit-router.js        |   59 ------
 core/modules/edit/js/theme.js                      |   25 ---
 core/modules/edit/js/views/contextuallink-view.js  |  109 +++++++++++
 core/modules/edit/js/views/menu-view.js            |   74 --------
 core/modules/edit/js/views/modal-view.js           |   26 +--
 core/modules/edit/js/views/overlay-view.js         |   86 ---------
 .../edit/js/views/propertyeditordecoration-view.js |   20 ++
 core/modules/edit/js/views/toolbar-view.js         |   18 +-
 .../Drupal/filter/Tests/FilterFormatAccessTest.php |    9 +
 .../node/lib/Drupal/node/NodeRenderController.php  |    6 +-
 .../node/lib/Drupal/node/Tests/PageEditTest.php    |   16 +-
 core/modules/node/node.module                      |    3 +-
 core/modules/system/system.module                  |    4 +-
 .../lib/Drupal/taxonomy/Tests/TermTest.php         |    9 +-
 33 files changed, 566 insertions(+), 632 deletions(-)

diff --git a/core/misc/edit-active.png b/core/misc/edit-active.png
new file mode 100644
index 0000000..6dd158d
--- /dev/null
+++ b/core/misc/edit-active.png
@@ -0,0 +1,8 @@
+PNG
+
+   IHDR         Ĵl;   IDATxA0JL
+0	$ 	%l8q[)DD8U5'
+/rc^૵
+rthgzΨ~
+<vvvOߣ&5
+h ̇üyuA9A: *"IzX;2c@=P;vB    IENDB`
\ No newline at end of file
diff --git a/core/misc/edit.png b/core/misc/edit.png
new file mode 100644
index 0000000..eb93320
--- /dev/null
+++ b/core/misc/edit.png
@@ -0,0 +1,7 @@
+PNG
+
+   IHDR         Ĵl;  4IDATxڥщ@aKRQp	",){ %sav͜gAz$P؛xMp(
+{^(.v}ȡv(B:UU	P?ڣF t@a
+0ᜳ@ahkxZ	hKx\
+hGxX԰ G;\`HaPx4f	@`x:ސ}WÜ£pYC
+'L&$40hpJ!q@gx4ݐQޅwa]d!Ƶ    IENDB`
\ No newline at end of file
diff --git a/core/modules/contextual/contextual.js b/core/modules/contextual/contextual.js
index fae8488..00c5bdf 100644
--- a/core/modules/contextual/contextual.js
+++ b/core/modules/contextual/contextual.js
@@ -7,6 +7,8 @@
 
 "use strict";
 
+var contextuals = [];
+
 /**
  * Attaches outline behavior for regions associated with contextual links.
  */
@@ -14,7 +16,14 @@ Drupal.behaviors.contextual = {
   attach: function (context) {
     $('ul.contextual-links', context).once('contextual', function () {
       var $this = $(this);
-      $this.data('drupal-contextual', new Drupal.contextual($this, $this.closest('.contextual-region')));
+      var contextual = new Drupal.contextual($this, $this.closest('.contextual-region'));
+      contextuals.push(contextual);
+      $this.data('drupal-contextual', contextual);
+    });
+
+    // Bind to edit mode changes.
+    $('body').once('contextual', function () {
+      $(document).on('drupalEditModeChanged.contextual', toggleEditMode);
     });
   }
 };
@@ -54,16 +63,33 @@ Drupal.contextual.prototype.init = function() {
     .attr('aria-pressed', false)
     .prependTo(this.$wrapper);
 
+  // The trigger behaviors are never detached or mutated.
+  this.$region
+    .on('click.contextual', '.contextual .trigger', $.proxy(this.triggerClickHandler, this))
+    .on('mouseleave.contextual', '.contextual', {show: false}, $.proxy(this.triggerLeaveHandler, this));
+  // Attach highlight behaviors.
+  this.attachHighlightBehaviors();
+};
+
+/**
+ * Attaches highlight-on-mouseenter behaviors.
+ */
+Drupal.contextual.prototype.attachHighlightBehaviors = function () {
   // Bind behaviors through delegation.
   var highlightRegion = $.proxy(this.highlightRegion, this);
   this.$region
-    .on('click.contextual', '.contextual .trigger', $.proxy(this.triggerClickHandler, this))
-    .on('mouseenter.contextual', {highlight: true}, highlightRegion)
-    .on('mouseleave.contextual', {highlight: false}, highlightRegion)
-    .on('mouseleave.contextual', '.contextual', {show: false}, $.proxy(this.triggerLeaveHandler, this))
-    .on('click.contextual', '.contextual-links a', {highlight: false}, highlightRegion)
-    .on('focus.contextual', '.contextual-links a, .contextual .trigger', {highlight: true}, highlightRegion)
-    .on('blur.contextual', '.contextual-links a, .contextual .trigger', {highlight: false}, highlightRegion);
+    .on('mouseenter.contextual.highlight', {highlight: true}, highlightRegion)
+    .on('mouseleave.contextual.highlight', {highlight: false}, highlightRegion)
+    .on('click.contextual.highlight', '.contextual-links a', {highlight: false}, highlightRegion)
+    .on('focus.contextual.highlight', '.contextual-links a, .contextual .trigger', {highlight: true}, highlightRegion)
+    .on('blur.contextual.highlight', '.contextual-links a, .contextual .trigger', {highlight: false}, highlightRegion);
+};
+
+/**
+ * Detaches unhighlight-on-mouseleave behaviors.
+ */
+Drupal.contextual.prototype.detachHighlightBehaviors = function () {
+  this.$region.off('.contextual.highlight');
 };
 
 /**
@@ -140,6 +166,16 @@ Drupal.contextual.prototype.showLinks = function(show) {
 };
 
 /**
+ * Shows or hides all pencil icons and corresponding contextual regions.
+ */
+function toggleEditMode (event, data) {
+  for (var i = contextuals.length - 1; i >= 0; i--) {
+    contextuals[i][(data.status) ? 'detachHighlightBehaviors' : 'attachHighlightBehaviors']();
+    contextuals[i].$region.toggleClass('contextual-region-active', data.status);
+  }
+}
+
+/**
  * Wraps contextual links.
  *
  * @return {String}
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index 30dcacb..76e39ba 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -6,6 +6,41 @@
  */
 
 /**
+ * Implements hook_toolbar().
+ */
+function contextual_toolbar() {
+  if (!user_access('access contextual links')) {
+    return;
+  }
+
+  $tab['contextual'] = array(
+    '#type' => 'toolbar_item',
+    'tab' => array(
+      '#type' => 'html_tag',
+      '#tag' => 'button',
+      '#value' => t('Edit'),
+      '#attributes' => array(
+        'class' => array('icon', 'icon-edit'),
+        'role' => 'button',
+        'aria-pressed' => 'false',
+      ),
+      // @todo remove this once http://drupal.org/node/1908906 lands.
+      '#options' => array('attributes' => array()),
+    ),
+    '#wrapper_attributes' => array(
+      'class' => array('element-hidden', 'contextual-toolbar-tab'),
+    ),
+    '#attached' => array(
+      'library' => array(
+        array('contextual', 'drupal.contextual-toolbar'),
+      ),
+    ),
+  );
+
+  return $tab;
+}
+
+/**
  * Implements hook_help().
  */
 function contextual_help($path, $arg) {
@@ -45,7 +80,9 @@ function contextual_library_info() {
     'website' => 'http://drupal.org/node/473268',
     'version' => VERSION,
     'js' => array(
-      $path . '/contextual.js' => array(),
+      // Add the JavaScript, with a group and weight such that it will run
+      // before modules/contextual/contextual.toolbar.js.
+      $path . '/contextual.js' => array('group' => JS_LIBRARY, 'weight' => -2),
     ),
     'css' => array(
       $path . '/contextual.base.css' => array(),
@@ -57,6 +94,23 @@ function contextual_library_info() {
       array('system', 'jquery.once'),
     ),
   );
+  $libraries['drupal.contextual-toolbar'] = array(
+    'title' => 'Contextual Links Toolbar Tab',
+    'version' => VERSION,
+    'js' => array(
+      // Add the JavaScript, with a group and weight such that it will run
+      // before modules/overlay/overlay-parent.js.
+      $path . '/contextual.toolbar.js' => array('group' => JS_LIBRARY, 'weight' => -1),
+    ),
+    'css' => array(
+      $path . '/contextual.toolbar.css' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'jquery.once'),
+      array('system', 'backbone'),
+    ),
+  );
 
   return $libraries;
 }
diff --git a/core/modules/contextual/contextual.theme-rtl.css b/core/modules/contextual/contextual.theme-rtl.css
index ed48367..e04a21f 100644
--- a/core/modules/contextual/contextual.theme-rtl.css
+++ b/core/modules/contextual/contextual.theme-rtl.css
@@ -16,6 +16,8 @@
  */
 .contextual .trigger {
   float: left;
+  right: 0;
+  left: 2px;
 }
 
 /**
diff --git a/core/modules/contextual/contextual.theme.css b/core/modules/contextual/contextual.theme.css
index aca019c..7c5cde4 100644
--- a/core/modules/contextual/contextual.theme.css
+++ b/core/modules/contextual/contextual.theme.css
@@ -10,39 +10,39 @@
   position: absolute;
   right: 0; /* LTR */
   top: 2px;
-  z-index: 999;
-}
-.contextual-region-active {
-  outline: 1px dashed #d6d6d6;
-  outline-offset: 1px;
+  z-index: 500;
 }
 
 /**
  * Contextual trigger.
  */
 .contextual .trigger {
-  background: transparent url("images/gear-select.png") no-repeat 2px 0;
-  border: 1px solid transparent;
-  border-radius: 4px 4px 0 0;
+  background-attachment: scroll;
+  background-color: #fff;
+  background-image: url("../../misc/edit.png");
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: 16px 16px;
+  border: 1px solid #ddd;
+  border-radius: 13px;
+  box-shadow: 1px 1px 2px rgba(0,0,0,0.3);
   /* Override the .element-focusable height: auto */
-  height: 18px !important;
+  height: 28px !important;
   float: right; /* LTR */
   margin: 0;
   overflow: hidden;
   padding: 0 2px;
   position: relative;
-  width: 34px;
+  right: 2px; /* LTR */
+  width: 28px;
   text-indent: -9999px;
   z-index: 2;
+  cursor: pointer;
 }
-.no-touch .contextual .trigger:hover,
 .contextual-links-active .trigger {
-  background-position: 2px -18px;
-}
-.contextual-links-active .trigger {
-  background-color: #fff;
-  border-bottom: none;
-  border-color: #d6d6d6;
+  border-bottom-color: transparent;
+  border-radius: 13px 13px 0 0;
+  box-shadow: none;
 }
 
 /**
@@ -52,7 +52,7 @@
  */
 .contextual-region .contextual .contextual-links {
   background-color: #fff;
-  border: 1px solid #d6d6d6;
+  border: 1px solid #ddd;
   border-radius: 4px 0 4px 4px; /* LTR */
   clear: both;
   float: right; /* LTR */
@@ -90,5 +90,7 @@
   text-decoration: none;
 }
 .no-touch .contextual-region .contextual .contextual-links li a:hover {
-  background-color: #bfdcee;
+  color: white;
+  background-image: -webkit-linear-gradient(rgb(78,159,234) 0%,rgb(65,126,210) 100%);
+  background-image: linear-gradient(rgb(78,159,234) 0%,rgb(65,126,210) 100%);
 }
diff --git a/core/modules/contextual/contextual.toolbar-rtl.css b/core/modules/contextual/contextual.toolbar-rtl.css
new file mode 100644
index 0000000..226969f
--- /dev/null
+++ b/core/modules/contextual/contextual.toolbar-rtl.css
@@ -0,0 +1,11 @@
+/**
+ * @file
+ * RTL styling for contextual module's toolbar tab.
+ */
+
+.js .toolbar .bar .contextual-toolbar-tab.tab {
+  float: left;
+}
+.js .toolbar .bar .contextual-toolbar-tab button {
+  padding-right: 1.3333em;
+}
diff --git a/core/modules/contextual/contextual.toolbar.css b/core/modules/contextual/contextual.toolbar.css
new file mode 100644
index 0000000..f7611f3
--- /dev/null
+++ b/core/modules/contextual/contextual.toolbar.css
@@ -0,0 +1,36 @@
+/**
+ * @file
+ * Styling for contextual module's toolbar tab.
+ */
+
+/* Tab icon. */
+.icon-edit:before {
+  background-image: url("../../misc/edit.png");
+}
+.icon-edit:active:before,
+.active.icon-edit:before {
+  background-image: url("../../misc/edit-active.png");
+}
+
+/* Tab appearance. */
+.js .toolbar .bar .contextual-toolbar-tab.tab {
+  float: right; /* LTR */
+}
+.js .toolbar .bar .contextual-toolbar-tab button {
+  padding-bottom: 1em;
+  padding-top: 1em;
+  /* Hide tab text. */
+  padding-left: 1.3333em; /* LTR */
+  text-indent: -9999px;
+}
+.js .toolbar .bar .contextual-toolbar-tab button.active {
+  background-image:-moz-linear-gradient(rgb(78,159,234) 0%,rgb(69,132,221) 100%);
+  background-image:-webkit-gradient(linear,color-stop(0, rgb(78,159,234)),color-stop(1, rgb(69,132,221)));
+  background-image: -webkit-linear-gradient(top,  rgb(78,159,234) 0%, rgb(69,132,221) 100%);
+  background-image:linear-gradient(rgb(78,159,234) 0%,rgb(69,132,221) 100%);
+}
+
+/* @todo get rid of this declaration by making toolbar.module's CSS less specific */
+.js .toolbar .bar .contextual-toolbar-tab.tab.element-hidden {
+  display: none;
+}
diff --git a/core/modules/contextual/contextual.toolbar.js b/core/modules/contextual/contextual.toolbar.js
new file mode 100644
index 0000000..45f9757
--- /dev/null
+++ b/core/modules/contextual/contextual.toolbar.js
@@ -0,0 +1,132 @@
+/**
+ * @file
+ * Attaches behaviors for the Contextual module's edit toolbar tab.
+ */
+
+(function ($, Backbone, Drupal, document, localStorage) {
+
+"use strict";
+
+/**
+ * Attaches contextual's edit toolbar tab behavior.
+ *
+ * Events
+ *   Contextual triggers an event that can be used by other scripts.
+ *   - drupalEditModeChanged: Triggered when the edit mode changes.
+ */
+Drupal.behaviors.contextualToolbar = {
+  attach: function (context) {
+    $('body').once('contextualToolbar-init', function () {
+      var $contextuals = $(context).find('.contextual-links');
+      var $tab = $('.js .toolbar .bar .contextual-toolbar-tab');
+      var model = new Drupal.contextualToolbar.models.EditToggleModel({
+        isViewing: true
+      });
+      var view = new Drupal.contextualToolbar.views.EditToggleView({
+        el: $tab,
+        model: model
+      });
+
+      // Update the model based on overlay events.
+      $(document)
+        .on('drupalOverlayOpen.contextualToolbar', function () {
+          model.set('isVisible', false);
+        })
+        .on('drupalOverlayClose.contextualToolbar', function () {
+          model.set('isVisible', true);
+        });
+
+      // Update the model to show the edit tab if there's >=1 contextual link.
+      if ($contextuals.length > 0) {
+        model.set('isVisible', true);
+      }
+
+      // Allow other scripts to respond to edit mode changes.
+      model.on('change:isViewing', function (model, value) {
+        $(document).trigger('drupalEditModeChanged', { status: !value });
+      });
+
+      // Checks whether localStorage indicates we should start in edit mode
+      // rather than view mode.
+      // @see Drupal.contextualToolbar.views.EditToggleView.persist()
+      if (localStorage.getItem('Drupal.contextualToolbar.isViewing') !== null) {
+        model.set('isViewing', false);
+      }
+    });
+  }
+};
+
+Drupal.contextualToolbar = Drupal.contextualToolbar || { models: {}, views: {}};
+
+/**
+ * Backbone Model for the edit toggle.
+ */
+Drupal.contextualToolbar.models.EditToggleModel = 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.
+    isVisible: false
+  }
+});
+
+/**
+ * Handles edit mode toggle interactions.
+ */
+Drupal.contextualToolbar.views.EditToggleView = Backbone.View.extend({
+
+  events: { 'click': 'onClick' },
+
+  /**
+   * Implements Backbone Views' initialize().
+   */
+  initialize: function () {
+    this.model.on('change', this.render, this);
+    this.model.on('change:isViewing', this.persist, this);
+  },
+
+  /**
+   * Implements Backbone Views' render().
+   */
+  render: function () {
+    var args = arguments;
+    // Render the visibility.
+    this.$el.toggleClass('element-hidden', !this.model.get('isVisible'));
+
+    // Render the state.
+    var isViewing = this.model.get('isViewing');
+    this.$el.find('button')
+      .toggleClass('active', !isViewing)
+      .attr('aria-pressed', !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.models.EditToggleModel model
+   *   An EditToggleModel Backbone 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');
+    }
+  },
+
+  onClick: function (event) {
+    this.model.set('isViewing', !this.model.get('isViewing'));
+    event.preventDefault();
+    event.stopPropagation();
+  }
+});
+
+})(jQuery, Backbone, Drupal, document, localStorage);
diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
index c255d34..7b758b7 100644
--- a/core/modules/edit/css/edit.css
+++ b/core/modules/edit/css/edit.css
@@ -71,47 +71,14 @@
 
 
 
-/**
- * Toolbar.
- */
-.icon-edit:before {
-  background-image: url("../images/icon-edit.png");
-}
-.icon-edit:active:before,
-.active .icon-edit:before {
-  background-image: url("../images/icon-edit-active.png");
-}
-.js .toolbar .bar .edit-toolbar-tab.tab {
-  float: right;
-}
-.toolbar .icon-edit.edit-nothing-editable-hidden {
-  display: none;
-}
-/* In-place editing doesn't work in the overlay, so always hide the tab. */
-.overlay-open .toolbar .icon-edit {
-  display: none;
-}
-
-
 
 /**
- * Edit mode: overlay + candidate editables + editables being edited.
+ * Candidate editables + editables being edited.
  *
  * Note: every class is prefixed with "edit-" to prevent collisions with modules
  * or themes. In IPE-specific DOM subtrees, this is not necessary.
  */
 
-#edit_overlay {
-  position: fixed;
-  z-index: 250;
-  width: 100%;
-  height: 100%;
-  background-color: #fff;
-  background-color: rgba(255,255,255,.5);
-  top: 0;
-  left: 0;
-}
-
 /* Editable. */
 .edit-editable {
   z-index: 300;
@@ -127,6 +94,7 @@
 
 /* Highlighted (hovered) editable. */
 .edit-editable.edit-highlighted {
+  z-index: 305;
   min-width: 200px;
 }
 .edit-field.edit-editable.edit-highlighted,
@@ -184,16 +152,6 @@
   background: #f5f5f5;
 }
 
-/* Modal active: prevent user from interacting with toolbar & editables. */
-.edit-form-container.edit-belowoverlay,
-.edit-toolbar-container.edit-belowoverlay,
-.edit-validation-errors.edit-belowoverlay {
-  z-index: 210;
-}
-.edit-editable.edit-belowoverlay {
-  z-index: 200;
-}
-
 
 
 
@@ -279,6 +237,10 @@
   bottom: 1px;
   box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5);
   background: #fff;
+  display: none;
+}
+.edit-highlighted .edit-toolbar-heightfaker {
+  display: block;
 }
 
 /* The toolbar; these are not necessarily visible. */
diff --git a/core/modules/edit/edit.info b/core/modules/edit/edit.info
index 4074a7b..7c6d4b9 100644
--- a/core/modules/edit/edit.info
+++ b/core/modules/edit/edit.info
@@ -3,4 +3,5 @@ description = In-place content editing.
 package = Core
 core = 8.x
 version = VERSION
+dependencies[] = contextual
 dependencies[] = field
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index dc1eec7..e4a3f82 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -39,42 +39,23 @@ function edit_permission() {
 }
 
 /**
- * Implements hook_toolbar().
+ * Implements hook_contextual_links_view_alter().
+ *
+ * In-place editing builds upon contextual.module, but doesn't actually add its
+ * "Quick edit" contextual link in PHP (i.e. here) because:
+ *  - that would require to add a local task menu item in the menu system, which
+ *    doesn't make any sense, since there is no corresponding page;
+ *  - it should only work when JavaScript is enabled, because only then in-place
+ *    editing is possible.
  */
-function edit_toolbar() {
+function edit_contextual_links_view_alter(&$element, $items) {
   if (!user_access('access in-place editing')) {
     return;
   }
 
-  $tab['edit'] = array(
-    '#type' => 'toolbar_item',
-    'tab' => array(
-      '#type' => 'link',
-      '#title' => t('Edit'),
-      '#href' => '',
-      '#options' => array(
-        'html' => FALSE,
-        'attributes' => array(
-          'id' => 'toolbar-tab-edit',
-          'class' => array('icon', 'icon-edit', 'edit-nothing-editable-hidden'),
-        ),
-      ),
-    ),
-    '#wrapper_attributes' => array(
-      'class' => array('edit-toolbar-tab'),
-    ),
-    '#attached' => array(
-      'library' => array(
-        array('edit', 'edit'),
-      ),
-    ),
-  );
-
   // Include the attachments and settings for all available editors.
   $attachments = drupal_container()->get('edit.editor.selector')->getAllEditorAttachments();
-  $tab['edit']['#attached'] = NestedArray::mergeDeep($tab['edit']['#attached'], $attachments);
-
-  return $tab;
+  $element['#attached'] = NestedArray::mergeDeep($element['#attached'], $attachments);
 }
 
 /**
@@ -94,15 +75,12 @@ function edit_library_info() {
       // Core.
       $path . '/js/edit.js' => $options,
       $path . '/js/app.js' => $options,
-      // Routers.
-      $path . '/js/routers/edit-router.js' => $options,
       // Models.
       $path . '/js/models/edit-app-model.js' => $options,
       // Views.
       $path . '/js/views/propertyeditordecoration-view.js' => $options,
-      $path . '/js/views/menu-view.js' => $options,
+      $path . '/js/views/contextuallink-view.js' => $options,
       $path . '/js/views/modal-view.js' => $options,
-      $path . '/js/views/overlay-view.js' => $options,
       $path . '/js/views/toolbar-view.js' => $options,
       // Backbone.sync implementation on top of Drupal forms.
       $path . '/js/backbone.drupalform.js' => $options,
@@ -174,6 +152,16 @@ function edit_preprocess_field(&$variables) {
 }
 
 /**
+ * Implements hook_preprocess_HOOK() for node.tpl.php.
+ *
+ * @todo Move towards hook_preprocess_entity() once that's available.
+ */
+function edit_preprocess_node(&$variables) {
+  $node = $variables['elements']['#node'];
+  $variables['attributes']['data-edit-entity'] = 'node/' . $node->nid;
+}
+
+/**
  * Form constructor for the field editing form.
  *
  * @ingroup forms
diff --git a/core/modules/edit/images/icon-edit-active.png b/core/modules/edit/images/icon-edit-active.png
deleted file mode 100644
index ad84761..0000000
--- a/core/modules/edit/images/icon-edit-active.png
+++ /dev/null
@@ -1,3 +0,0 @@
-PNG
-
-   IHDR         j	   `PLTE   [   tRNS@ P00p`ϟ Dƙ   IDATxe DQ8Ϩ/BDU9xV+D\?x@qWcF8wicS B}?v;Vf.V$JgX=Kضp0XS"iRw\:LL\~;Z5wu 5E)L    IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/images/icon-edit.png b/core/modules/edit/images/icon-edit.png
deleted file mode 100644
index 4f0dcc2..0000000
--- a/core/modules/edit/images/icon-edit.png
+++ /dev/null
@@ -1,5 +0,0 @@
-PNG
-
-   IHDR         j	   PLTE̻ʪ̡̜ˠ̣̽¼Ƿ   ʨªZ(e   +tRNSϟ `π@`0p0p`0Pϟϟ c   IDATxeW0 {W
-H"ʵ,y{Hpyo?mf,RBRxBvL;&LPJaRb\(Tbn(1wϔJ)ԈkS
-58äT^4 P6c}[i	<ާ'-+HP>K    IENDB`
\ No newline at end of file
diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js
index 00bba20..07f006f 100644
--- a/core/modules/edit/js/app.js
+++ b/core/modules/edit/js/app.js
@@ -47,20 +47,8 @@
         editableNs: 'createeditable'
       });
 
-      // Instantiate OverlayView.
-      var overlayView = new Drupal.edit.views.OverlayView({
-        el: (Drupal.theme('editOverlay', {})),
-        model: this.model
-      });
-
-      // Instantiate MenuView.
-      var editMenuView = new Drupal.edit.views.MenuView({
-        el: this.el,
-        model: this.model
-      });
-
       // When view/edit mode is toggled in the menu, update the editor widgets.
-      this.model.on('change:isViewing', this.appStateChange);
+      this.model.on('change:activeEntity', this.appStateChange);
     },
 
     /**
@@ -74,7 +62,7 @@
      */
     findEditableProperties: function($context) {
       var that = this;
-      var newState = (this.model.get('isViewing')) ? 'inactive' : 'candidate';
+      var activeEntity = this.model.get('activeEntity');
 
       this.domService.findSubjectElements($context).each(function() {
         var $element = $(this);
@@ -103,10 +91,17 @@
           .on('destroyedPropertyEditor.edit', function(event, editor) {
             that.undecorateEditor(editor);
             that.$entityElements = that.$entityElements.not($(this));
-
           })
-          // Transition the new PropertyEditor into the current state.
-          .createEditable('setState', newState);
+          // Transition the new PropertyEditor into the default state.
+          .createEditable('setState', 'inactive');
+
+        // If the new PropertyEditor is for the entity that's currently being
+        // edited, then transition it to the 'candidate' state.
+        // (This happens when a field was modified and is re-rendered.)
+        var entityOfProperty = $element.createEditable('option', 'model');
+        if (entityOfProperty.getSubjectUri() === activeEntity) {
+          $element.createEditable('setState', 'candidate');
+        }
 
         // Add this new EditableEntity widget element to the list.
         that.$entityElements = that.$entityElements.add($element);
@@ -116,26 +111,29 @@
     /**
      * Sets the state of PropertyEditor widgets when edit mode begins or ends.
      *
-     * Should be called whenever EditAppModel's "isViewing" changes.
+     * Should be called whenever EditAppModel's "activeEntity" changes.
      */
     appStateChange: function() {
       // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140)
       // We're currently setting the state on EditableEntity widgets instead of
       // PropertyEditor widgets, because of
       // https://github.com/bergie/create/issues/133.
-      var newState = (this.model.get('isViewing')) ? 'inactive' : 'candidate';
+
+      var activeEntity = this.model.get('activeEntity');
+      var $editableFieldsForEntity = $('[data-edit-id^="' + activeEntity + '/"]');
+
+      // First, change the status of all PropertyEditor widgets to 'inactive'.
       this.$entityElements.each(function() {
-        $(this).createEditable('setState', newState);
+        $(this).createEditable('setState', 'inactive', null, {reason: 'stop'});
+      });
+
+      // Then, change the status of PropertyEditor widgets of the currently
+      // active entity to 'candidate'.
+      $editableFieldsForEntity.each(function() {
+        $(this).createEditable('setState', 'candidate');
       });
+
       // Manage the page's tab indexes.
-      if (newState === 'candidate') {
-        this._manageDocumentFocus();
-        Drupal.edit.setMessage(Drupal.t('In place edit mode is active'), Drupal.t('Page navigation is limited to editable items.'), Drupal.t('Press escape to exit'));
-      }
-      else if (newState === 'inactive') {
-        this._releaseDocumentFocusManagement();
-        Drupal.edit.setMessage(Drupal.t('Edit mode is inactive.'), Drupal.t('Resume normal page navigation'));
-      }
     },
 
     /**
@@ -159,9 +157,9 @@
 
       // If the app is in view mode, then reject all state changes except for
       // those to 'inactive'.
-      if (this.model.get('isViewing')) {
-        if (to !== 'inactive') {
-          accept = false;
+      if (context && context.reason === 'stop') {
+        if (from === 'candidate' && to === 'inactive') {
+          accept = true;
         }
       }
       // Handling of edit mode state changes is more granular.
@@ -314,7 +312,6 @@
       // Keep track of the active editor in the global state.
       if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) {
         this.model.set('activeEditor', editor);
-        Drupal.edit.setMessage(Drupal.t('An editor is active'));
       }
       else if (this.model.get('activeEditor') === editor && to === 'candidate') {
         // Discarded if it transitions from a changed state to 'candidate'.
@@ -387,142 +384,8 @@
       // Don't call .remove() on the decoration view, because that would remove
       // a potentially rerendered field.
       delete editor.decorationView;
-    },
-
-    /**
-     * Makes elements other than the editables unreachable via the tab key.
-     *
-     * @todo refactoring.
-     *
-     * This method is currently overloaded, handling elements of state modeling
-     * and application control. The state of the application is spread between
-     * this view, its model and aspects of the UI widgets in Create.js. In order
-     * to drive focus management from the application state (and have it
-     * influence that state of the application), we need to distall state out
-     * of Create.js components.
-     *
-     * This method introduces behaviors that support accessibility of the edit
-     * application. Although not yet integrated into the application properly,
-     * it does provide us with the opportunity to collect feedback from
-     * users who will interact with edit primarily through keyboard input. We
-     * want this feedback sooner than we can have a refactored application.
-     */
-    _manageDocumentFocus: function () {
-      var editablesSelector = '.edit-candidate.edit-editable';
-      var inputsSelector = 'a:visible, button:visible, input:visible, textarea:visible, select:visible';
-      var $editables = $(editablesSelector)
-        .attr({
-          'tabindex': 0,
-          'role': 'button'
-        });
-      // Instantiate a variable to hold the editable element in the set.
-      var $currentEditable;
-      // We're using simple function scope to manage 'this' for the internal
-      // handler, so save this as that.
-      var that = this;
-      // Turn on focus management.
-      $(document).on('keydown.edit', function (event) {
-        var activeEditor, editableEntity, predicate;
-        // Handle esc key press. Close any active editors.
-        if (event.keyCode === 27) {
-          event.preventDefault();
-          activeEditor = that.model.get('activeEditor');
-          if (activeEditor) {
-            editableEntity = activeEditor.options.widget;
-            predicate = activeEditor.options.property;
-            editableEntity.setState('candidate', predicate, { reason: 'overlay' });
-          }
-          else {
-            $(editablesSelector).trigger('tabOut.edit');
-            // This should move into the state management for the app model.
-            location.hash = "#view";
-            that.model.set('isViewing', true);
-          }
-          return;
-        }
-        // Handle enter or space key presses.
-        if (event.keyCode === 13 || event.keyCode === 32) {
-          if ($currentEditable && $currentEditable.is(editablesSelector)) {
-            $currentEditable.trigger('click');
-            // Squelch additional handlers.
-            event.preventDefault();
-            return;
-          }
-        }
-        // Handle tab key presses.
-        if (event.keyCode === 9) {
-          var context = '';
-          // Include the view mode toggle with the editables selector.
-          var selector = editablesSelector + ', #toolbar-tab-edit';
-          activeEditor = that.model.get('activeEditor');
-          var $confirmDialog = $('#edit_modal');
-          // If the edit modal is active, that is the tabbing context.
-          if ($confirmDialog.length) {
-            context = $confirmDialog;
-            selector = inputsSelector;
-            if (!$currentEditable || $currentEditable.is(editablesSelector)) {
-              $currentEditable = $(selector, context).eq(-1);
-            }
-          }
-          // If an editor is active, then the tabbing context is the editor and
-          // its toolbar.
-          else if (activeEditor) {
-            context = $(activeEditor.$formContainer).add(activeEditor.toolbarView.$el);
-            // Include the view mode toggle with the editables selector.
-            selector = inputsSelector;
-            if (!$currentEditable || $currentEditable.is(editablesSelector)) {
-              $currentEditable = $(selector, context).eq(-1);
-            }
-          }
-          // Otherwise the tabbing context is the list of editable predicates.
-          var $editables = $(selector, context);
-          if (!$currentEditable) {
-            $currentEditable = $editables.eq(-1);
-          }
-          var count = $editables.length - 1;
-          var index = $editables.index($currentEditable);
-          // Navigate backwards.
-          if (event.shiftKey) {
-            // Beginning of the set, loop to the end.
-            if (index === 0) {
-              index = count;
-            }
-            else {
-              index -= 1;
-            }
-          }
-          // Navigate forewards.
-          else {
-            // End of the set, loop to the start.
-            if (index === count) {
-              index = 0;
-            }
-            else {
-              index += 1;
-            }
-          }
-          // Tab out of the current editable.
-          $currentEditable.trigger('tabOut.edit');
-          // Update the current editable.
-          $currentEditable = $editables
-            .eq(index)
-            .focus()
-            .trigger('tabIn.edit');
-          // Squelch additional handlers.
-          event.preventDefault();
-          event.stopPropagation();
-        }
-      });
-      // Set focus on the edit button initially.
-      $('#toolbar-tab-edit').focus();
-    },
-    /**
-     * Removes key management and edit accessibility features from the DOM.
-     */
-    _releaseDocumentFocusManagement: function () {
-      $(document).off('keydown.edit');
-      $('.edit-allowed.edit-field').removeAttr('tabindex role');
     }
+
   });
 
 })(jQuery, _, Backbone, Drupal, VIE);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
index 5671f39..caac604 100644
--- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
+++ b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
@@ -29,13 +29,6 @@
     _initialize: function() {
       var that = this;
 
-      // Sets the state to 'activated' upon clicking the element.
-      this.element.on("click.edit", function(event) {
-        event.stopPropagation();
-        event.preventDefault();
-        that.options.activated();
-      });
-
       // Sets the state to 'changed' whenever the content has changed.
       var before = jQuery.trim(this.element.text());
       this.element.on('keyup paste', function (event) {
@@ -68,6 +61,7 @@
         case 'highlighted':
           break;
         case 'activating':
+          this.options.activated();
           break;
         case 'active':
           // Sets the "contenteditable" attribute to "true".
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
index 3238566..83dad4f 100644
--- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js
+++ b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
@@ -29,15 +29,7 @@
     /**
      * Implements Create's _initialize() method.
      */
-    _initialize: function() {
-      // Sets the state to 'activating' upon clicking the element.
-      var that = this;
-      this.element.on("click.edit", function(event) {
-        event.stopPropagation();
-        event.preventDefault();
-        that.options.activating();
-      });
-    },
+    _initialize: function() {},
 
     /**
      * Makes this PropertyEditor widget react to state changes.
@@ -49,15 +41,11 @@
         case 'candidate':
           if (from !== 'inactive') {
             this.disable();
-            if (from !== 'highlighted') {
-              this.element.removeClass('edit-belowoverlay');
-            }
           }
           break;
         case 'highlighted':
           break;
         case 'activating':
-          this.element.addClass('edit-belowoverlay');
           this.enable();
           break;
         case 'active':
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index cfaf76b..f924e7b 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -6,16 +6,6 @@
 
 "use strict";
 
-/**
- * The edit ARIA live message area.
- *
- * @todo Eventually the messages area should be converted into a Backbone View
- * that will respond to changes in the application's model. For the initial
- * implementation, we will call the Drupal.edit.setMessage method when an aural
- * message should be read by the user agent.
- */
-var $messages;
-
 Drupal.edit = Drupal.edit || {};
 Drupal.edit.metadataCache = Drupal.edit.metadataCache || {};
 
@@ -28,7 +18,7 @@ Drupal.behaviors.edit = {
     var $fields = $context.find('[data-edit-id]');
 
     // Initialize the Edit app.
-    $context.find('#toolbar-tab-edit').once('edit-init', Drupal.edit.init);
+    $('body').once('edit-init', Drupal.edit.init);
 
     var annotateField = function(field) {
       if (_.has(Drupal.edit.metadataCache, field.editID)) {
@@ -81,13 +71,6 @@ Drupal.behaviors.edit = {
             // Annotate the remaining fields based on the updated access cache.
             _.each(remainingFieldsToAnnotate, annotateField);
 
-            // As soon as there is at least one editable field, show the Edit
-            // tab in the toolbar.
-            if ($fields.filter('.edit-allowed').length) {
-              $('.toolbar .icon-edit.edit-nothing-editable-hidden')
-                .removeClass('edit-nothing-editable-hidden');
-            }
-
             // Find editable fields, make them editable.
             Drupal.edit.app.findEditableProperties($context);
           }
@@ -98,9 +81,6 @@ Drupal.behaviors.edit = {
 };
 
 Drupal.edit.init = function() {
-  // Append a messages element for appending interaction updates for screen
-  // readers.
-  $messages = $(Drupal.theme('editMessageBox')).appendTo($(this).parent());
   // Instantiate EditAppView, which is the controller of it all. EditAppModel
   // instance tracks global state (viewing/editing in-place).
   var appModel = new Drupal.edit.models.EditAppModel();
@@ -109,32 +89,25 @@ Drupal.edit.init = function() {
     model: appModel
   });
 
-  // Instantiate EditRouter.
-  var editRouter = new Drupal.edit.routers.EditRouter({
-    appModel: appModel
+  // Add "Quick edit" links to all contextual menus where editing the full
+  // node is possible.
+  // @todo Generalize this to work for all entities.
+  $('ul.contextual-links li.node-edit')
+  .before('<li class="quick-edit"></li>')
+  .each(function() {
+    // Instantiate ContextualLinkView.
+    var $editContextualLink = $(this).prev();
+    var editContextualLinkView = new Drupal.edit.views.ContextualLinkView({
+      el: $editContextualLink.get(0),
+      model: appModel,
+      entity: $editContextualLink.parents('[data-edit-entity]').attr('data-edit-entity')
+    });
   });
 
-  // Start Backbone's history/route handling.
-  Backbone.history.start();
-
   // For now, we work with a singleton app, because for Drupal.behaviors to be
   // able to discover new editable properties that get AJAXed in, it must know
   // with which app instance they should be associated.
   Drupal.edit.app = app;
 };
 
-/**
- * Places the message in the edit ARIA live message area.
- *
- * The message will be read by speaking User Agents.
- *
- * @param {String} message
- *   A string to be inserted into the message area.
- */
-Drupal.edit.setMessage = function(message) {
-  var args = Array.prototype.slice.call(arguments);
-  args.unshift('editMessage');
-  $messages.html(Drupal.theme.apply(this, args));
-};
-
 })(jQuery, _, Backbone, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/models/edit-app-model.js b/core/modules/edit/js/models/edit-app-model.js
index b6ff36f..0c90fd0 100644
--- a/core/modules/edit/js/models/edit-app-model.js
+++ b/core/modules/edit/js/models/edit-app-model.js
@@ -10,8 +10,7 @@ Drupal.edit = Drupal.edit || {};
 Drupal.edit.models = Drupal.edit.models || {};
 Drupal.edit.models.EditAppModel = Backbone.Model.extend({
   defaults: {
-    // We always begin in view mode.
-    isViewing: true,
+    activeEntity: null,
     highlightedEditor: null,
     activeEditor: null,
     // Reference to a ModalView-instance if a transition requires confirmation.
diff --git a/core/modules/edit/js/routers/edit-router.js b/core/modules/edit/js/routers/edit-router.js
deleted file mode 100644
index d160ad4..0000000
--- a/core/modules/edit/js/routers/edit-router.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file
- * A Backbone Router enabling URLs to make the user enter edit mode directly.
- */
-(function(Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.routers = {};
-Drupal.edit.routers.EditRouter = Backbone.Router.extend({
-
-  appModel: null,
-
-  routes: {
-    "edit": "edit",
-    "view": "view",
-    "": "view"
-  },
-
-  initialize: function(options) {
-    this.appModel = options.appModel;
-
-    var that = this;
-    this.appModel.on('change:isViewing', function() {
-      that.navigate(that.appModel.get('isViewing') ? '#view' : '#edit');
-    });
-  },
-
-  edit: function() {
-    this.appModel.set('isViewing', false);
-  },
-
-  view: function(query, page) {
-    var that = this;
-
-    // If there's an active editor, attempt to set its state to 'candidate', and
-    // then act according to the user's choice.
-    var activeEditor = this.appModel.get('activeEditor');
-    if (activeEditor) {
-      var editableEntity = activeEditor.options.widget;
-      var predicate = activeEditor.options.property;
-      editableEntity.setState('candidate', predicate, { reason: 'menu' }, function(accepted) {
-        if (accepted) {
-          that.appModel.set('isViewing', true);
-        }
-        else {
-          that.appModel.set('isViewing', false);
-        }
-      });
-    }
-    // Otherwise, we can switch to view mode directly.
-    else {
-      that.appModel.set('isViewing', true);
-    }
-  }
-});
-
-})(Backbone, Drupal);
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
index 42f7613..f2452ba 100644
--- a/core/modules/edit/js/theme.js
+++ b/core/modules/edit/js/theme.js
@@ -152,29 +152,4 @@ Drupal.theme.editFormContainer = function(settings) {
   return html;
 };
 
-/**
- * A region to post messages that a screen reading UA will announce.
- *
- * @return {String}
- *   A string representing a DOM fragment.
- */
-Drupal.theme.editMessageBox = function() {
-  return '<div id="edit-messages" class="element-invisible" role="region" aria-live="polite"></div>';
-};
-
-/**
- * Wrap message strings in p tags.
- *
- * @return {String}
- *   A string representing a DOM fragment.
- */
-Drupal.theme.editMessage = function() {
-  var messages = Array.prototype.slice.call(arguments);
-  var output = '';
-  for (var i = 0; i < messages.length; i++) {
-   output += '<p>' + messages[i] + '</p>';
-  }
-  return output;
-};
-
 })(jQuery, Drupal);
diff --git a/core/modules/edit/js/views/contextuallink-view.js b/core/modules/edit/js/views/contextuallink-view.js
new file mode 100644
index 0000000..efe8ddd
--- /dev/null
+++ b/core/modules/edit/js/views/contextuallink-view.js
@@ -0,0 +1,109 @@
+/**
+ * @file
+ * A Backbone View that a dynamic contextual link.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.views = Drupal.edit.views || {};
+Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
+
+  entity: null,
+
+  events: {
+    'click': 'onClick'
+  },
+
+  /**
+   * Implements Backbone Views' initialize() function.
+   *
+   * @param options
+   *   An object with the following keys:
+   *   - entity: the entity ID (e.g. node/1) of the entity
+   */
+  initialize: function (options) {
+    this.entity = options.entity;
+
+    // Initial render.
+    this.render();
+
+    // Re-render whenever the app state's active entity changes.
+    this.model.on('change:activeEntity', this.render, this);
+
+    // Hide the contextual links whenever an in-place editor is active.
+    this.model.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
+  },
+
+  /**
+   * Equates clicks anywhere on the overlay to clicking the active editor's (if
+   * any) "close" button.
+   *
+   * @param {Object} event
+   */
+  onClick: function (event) {
+    event.preventDefault();
+
+    var that = this;
+    var updateActiveEntity = function() {
+      // The active entity is the current entity, i.e. stop editing the current
+      // entity.
+      if (that.model.get('activeEntity') === that.entity) {
+        that.model.set('activeEntity', null);
+      }
+      // The active entity is different from the current entity, i.e. start
+      // editing this entity instead of the previous one.
+      else {
+        that.model.set('activeEntity', that.entity);
+      }
+    };
+
+    // If there's an active editor, attempt to set its state to 'candidate', and
+    // only then do what the user asked.
+    // (Only when all PropertyEditor widgets of an entity are in the 'candidate'
+    // state, it is possible to stop editing it.)
+    var activeEditor = this.model.get('activeEditor');
+    if (activeEditor) {
+      var editableEntity = activeEditor.options.widget;
+      var predicate = activeEditor.options.property;
+      editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function(accepted) {
+        if (accepted) {
+          updateActiveEntity();
+        }
+        else {
+          // No change.
+        }
+      });
+    }
+    // Otherwise, we can immediately do what the user asked.
+    else {
+      updateActiveEntity();
+    }
+  },
+
+  /**
+   * Render the "Quick edit" contextual link.
+   */
+  render: function () {
+    var activeEntity = this.model.get('activeEntity');
+    var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit');
+    this.$el.html('<a href="">' + string + '</a>');
+    return this;
+  },
+
+  /**
+   * Model change handler; hides the contextual links if an editor is active.
+   *
+   * @param Drupal.edit.models.EditAppModel model
+   *   An EditAppModel model.
+   * @param jQuery|null activeEditor
+   *   The active in-place editor (jQuery object) or, if none, null.
+   */
+  toggleContextualLinksVisibility: function (model, activeEditor) {
+    this.$el.parents('.contextual').toggle(activeEditor === null);
+  }
+
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/menu-view.js b/core/modules/edit/js/views/menu-view.js
deleted file mode 100644
index 35cac3b..0000000
--- a/core/modules/edit/js/views/menu-view.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the app-level interactive menu.
- */
-(function($, _, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.MenuView = Backbone.View.extend({
-
-  events: {
-    'click #toolbar-tab-edit': 'editClickHandler'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   */
-  initialize: function() {
-    _.bindAll(this, 'stateChange');
-    this.model.on('change:isViewing', this.stateChange);
-    // Respond to clicks on other toolbar tabs. 
-    // @todo This temporary pending improvements to the toolbar module.
-    // @see https://drupal.org/node/1860434
-    $('#toolbar-administration').on('click.edit', '.bar a:not(#toolbar-tab-edit)', _.bind(function (event) {
-      this.model.set('isViewing', true);
-    }, this));
-    // We have to call stateChange() here because URL fragments are not passed
-    // to the server, thus the wrong anchor may be marked as active.
-    this.stateChange();
-  },
-
-  /**
-   * Listens to app state changes.
-   */
-  stateChange: function() {
-    var isViewing = this.model.get('isViewing');
-    // Toggle the state of the Toolbar Edit tab based on the isViewing state.
-    this.$el.find('#toolbar-tab-edit')
-      .toggleClass('active', !isViewing)
-      .attr('aria-pressed', !isViewing);
-    // Manage the toolbar state until
-    // https://drupal.org/node/1847198 is resolved
-    if (!isViewing) {
-      // Remove the 'toolbar-tray-open' class from the body element.
-      this.$el.removeClass('toolbar-tray-open');
-      // Deactivate any other active tabs and trays.
-      this.$el
-        .find('.bar a', '#toolbar-administration')
-        .not('#toolbar-tab-edit')
-        .add('.tray', '#toolbar-administration')
-        .removeClass('active');
-      // Set the height of the toolbar.
-      if ('toolbar' in Drupal) {
-        Drupal.toolbar.setHeight();
-      }
-    }
-  },
-  /**
-   * Handles clicks on the edit tab of the toolbar.
-   *
-   * @param {Object} event
-   */
-  editClickHandler: function (event) {
-    var isViewing = this.model.get('isViewing');
-    // Toggle the href of the Toolbar Edit tab based on the isViewing state. The
-    // href value should represent to state to be entered.
-    this.$el.find('#toolbar-tab-edit').attr('href', (isViewing) ? '#edit' : '#view');
-    this.model.set('isViewing', !isViewing);
-  }
-});
-
-})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js
index 2e3b49c..b98c876 100644
--- a/core/modules/edit/js/views/modal-view.js
+++ b/core/modules/edit/js/views/modal-view.js
@@ -43,17 +43,6 @@ Drupal.edit.views.ModalView = Backbone.View.extend({
    * Implements Backbone Views' render() function.
    */
   render: function() {
-    // Step 1: move certain UI elements below the overlay.
-    var editor = this.model.get('activeEditor');
-    this.$elementsToHide = $([])
-      .add((editor.element.hasClass('edit-belowoverlay')) ? null : editor.element)
-      .add(editor.toolbarView.$el)
-      .add((editor.options.editorName === 'form') ? editor.$formContainer : editor.element.next('.edit-validation-errors'));
-    this.$elementsToHide.addClass('edit-belowoverlay');
-
-    // Step 2: the modal. When the user makes a choice, the UI elements that
-    // were moved below the overlay will be restored, and the callback will be
-    // called.
     this.setElement(Drupal.theme('editModal', {}));
     this.$el.appendTo('body');
     // Template.
@@ -61,13 +50,11 @@ Drupal.edit.views.ModalView = Backbone.View.extend({
     var $actions = $(Drupal.theme('editButtons', { 'buttons' : this.buttons}));
     this.$('.actions').append($actions);
 
-    // Step 3; show the modal with an animation.
+    // Show the modal with an animation.
     var that = this;
     setTimeout(function() {
       that.$el.removeClass('edit-animate-invisible');
     }, 0);
-
-    Drupal.edit.setMessage(Drupal.t('Confirmation dialog open'));
   },
 
   /**
@@ -90,17 +77,6 @@ Drupal.edit.views.ModalView = Backbone.View.extend({
 
     var action = $(event.target).attr('data-edit-modal-action');
     return this.callback(action);
-  },
-
-  /**
-   * Overrides Backbone Views' remove() function.
-   */
-  remove: function() {
-    // Move the moved UI elements on top of the overlay again.
-    this.$elementsToHide.removeClass('edit-belowoverlay');
-
-    // Remove the modal itself.
-    this.$el.remove();
   }
 });
 
diff --git a/core/modules/edit/js/views/overlay-view.js b/core/modules/edit/js/views/overlay-view.js
deleted file mode 100644
index 2113ab8..0000000
--- a/core/modules/edit/js/views/overlay-view.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * @file
- * A Backbone View that provides the app-level overlay.
- *
- * The overlay sits on top of the existing content, the properties that are
- * candidates for editing sit on top of the overlay.
- */
-(function ($, _, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.OverlayView = Backbone.View.extend({
-
-  events: {
-    'click': 'onClick'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   */
-  initialize: function (options) {
-    _.bindAll(this, 'stateChange');
-    this.model.on('change:isViewing', this.stateChange);
-    // Add the overlay to the page.
-    this.$el
-      .addClass('edit-animate-slow edit-animate-invisible')
-      .hide()
-      .appendTo('body');
-  },
-
-  /**
-   * Listens to app state changes.
-   */
-  stateChange: function () {
-    if (this.model.get('isViewing')) {
-      this.remove();
-      return;
-    }
-    this.render();
-  },
-
-  /**
-   * Equates clicks anywhere on the overlay to clicking the active editor's (if
-   * any) "close" button.
-   *
-   * @param {Object} event
-   */
-  onClick: function (event) {
-    event.preventDefault();
-    var activeEditor = this.model.get('activeEditor');
-    if (activeEditor) {
-      var editableEntity = activeEditor.options.widget;
-      var predicate = activeEditor.options.property;
-      editableEntity.setState('candidate', predicate, { reason: 'overlay' });
-    }
-    else {
-      this.model.set('isViewing', true);
-    }
-  },
-
-  /**
-   * Reveal the overlay element.
-   */
-  render: function () {
-    this.$el
-      .show()
-      .css('top', $('#navbar').outerHeight())
-      .removeClass('edit-animate-invisible');
-  },
-
-  /**
-   * Hide the overlay element.
-   */
-  remove: function () {
-    var that = this;
-    this.$el
-      .addClass('edit-animate-invisible')
-      .on(Drupal.edit.util.constants.transitionEnd, function (event) {
-        that.$el.hide();
-      });
-  }
-});
-
-})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
index 0eb4e45..aad9832 100644
--- a/core/modules/edit/js/views/propertyeditordecoration-view.js
+++ b/core/modules/edit/js/views/propertyeditordecoration-view.js
@@ -19,6 +19,7 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
   events: {
     'mouseenter.edit' : 'onMouseEnter',
     'mouseleave.edit' : 'onMouseLeave',
+    'click': 'onClick',
     'tabIn.edit': 'onMouseEnter',
     'tabOut.edit': 'onMouseLeave'
   },
@@ -38,7 +39,12 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
     this.editor = options.editor;
     this.toolbarId = options.toolbarId;
 
+    this.predicate = this.editor.options.property;
+
     this.$el.css('background-color', this._getBgColor(this.$el));
+
+    // Only start listening to events as soon as we're no longer in the 'inactive' state.
+    this.undelegateEvents();
   },
 
   /**
@@ -113,13 +119,27 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
     });
   },
 
+  /**
+   * Clicks: transition to 'activating' stage.
+   *
+   * @param event
+   */
+  onClick: function(event) {
+    var editableEntity = this.editor.options.widget;
+    editableEntity.setState('activating', this.predicate);
+    event.preventDefault();
+    event.stopPropagation();
+  },
+
   decorate: function () {
     this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
+    this.delegateEvents();
   },
 
   undecorate: function () {
     this.$el
       .removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
+    this.undelegateEvents();
   },
 
   startHighlight: function () {
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
index 90f5db7..b052362 100644
--- a/core/modules/edit/js/views/toolbar-view.js
+++ b/core/modules/edit/js/views/toolbar-view.js
@@ -29,7 +29,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
     'click.edit button.label': 'onClickInfoLabel',
     'mouseleave.edit': 'onMouseLeave',
     'click.edit button.field-save': 'onClickSave',
-    'click.edit button.field-close': 'onClickClose'
+    'click.edit button.field-close': 'onClickClose',
   },
 
   /**
@@ -66,19 +66,26 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
   stateChange: function(from, to) {
     switch (to) {
       case 'inactive':
-        // Nothing happens in this stage.
+        if (from) {
+          this.remove();
+        }
         break;
       case 'candidate':
-        if (from !== 'inactive') {
+        if (from === 'inactive') {
+          this.render();
+        }
+        else {
+          // Remove all toolgroups; they're no longer necessary.
+          this.$el
+            .removeClass('edit-highlighted edit-editing')
+            .find('.edit-toolbar .edit-toolgroup').remove();
           if (from !== 'highlighted' && this.getEditUISetting('padding')) {
             this._unpad();
           }
-          this.remove();
         }
         break;
       case 'highlighted':
         // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
-        this.render();
         this.startHighlight();
         break;
       case 'activating':
@@ -275,6 +282,7 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
     }
 
     this.$el
+      .addClass('edit-highlighted')
       .find('.edit-toolbar')
       // Append the "info" toolgroup into the toolbar.
       .append(Drupal.theme('editToolgroup', {
diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php
index 48faf24..65dd7af 100644
--- a/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php
+++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php
@@ -48,6 +48,13 @@ class FilterFormatAccessTest extends WebTestBase {
    */
   protected $disallowed_format;
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('contextual');
+
   public static function getInfo() {
     return array(
       'name' => 'Filter format access',
@@ -88,6 +95,7 @@ function setUp() {
     $this->web_user = $this->drupalCreateUser(array(
       'create page content',
       'edit any page content',
+      'access contextual links',
       filter_permission_name($this->allowed_format),
     ));
 
@@ -96,6 +104,7 @@ function setUp() {
       'administer filters',
       'create page content',
       'edit any page content',
+      'access contextual links',
       filter_permission_name($this->allowed_format),
       filter_permission_name($this->disallowed_format),
     ));
diff --git a/core/modules/node/lib/Drupal/node/NodeRenderController.php b/core/modules/node/lib/Drupal/node/NodeRenderController.php
index e829e40..6a6608a 100644
--- a/core/modules/node/lib/Drupal/node/NodeRenderController.php
+++ b/core/modules/node/lib/Drupal/node/NodeRenderController.php
@@ -88,11 +88,7 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
    */
   protected function alterBuild(array &$build, EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode = NULL) {
     parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
-    // Add contextual links for this node, except when the node is already being
-    // displayed on its own page. Modules may alter this behavior (for example,
-    // to restrict contextual links to certain view modes) by implementing
-    // hook_node_view_alter().
-    if (!empty($entity->nid) && !($view_mode == 'full' && node_is_page($entity))) {
+    if (!empty($entity->nid)) {
       $build['#contextual_links']['node'] = array('node', array($entity->nid));
     }
   }
diff --git a/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php
index b461492..b650fea 100644
--- a/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php
@@ -14,6 +14,13 @@ class PageEditTest extends NodeTestBase {
   protected $web_user;
   protected $admin_user;
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'contextual');
+
   public static function getInfo() {
     return array(
       'name' => 'Node edit',
@@ -25,8 +32,8 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
-    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes'));
+    $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content', 'access contextual links'));
+    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'access contextual links'));
   }
 
   /**
@@ -50,14 +57,11 @@ function testPageEdit() {
 
     // Check that "edit" link points to correct page.
     $this->clickLink(t('Edit'));
-    $edit_url = url("node/$node->nid/edit", array('absolute' => TRUE));
+    $edit_url = url("node/$node->nid/edit", array('absolute' => TRUE, 'query' => array('destination' => 'node/1')));
     $actual_url = $this->getURL();
     $this->assertEqual($edit_url, $actual_url, 'On edit page.');
 
     // Check that the title and body fields are displayed with the correct values.
-    $active = '<span class="element-invisible">' . t('(active tab)') . '</span>';
-    $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active));
-    $this->assertText(strip_tags($link_text), 0, 'Edit tab found and marked active.');
     $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
     $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index f2206a0..ff073ae 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1769,7 +1769,7 @@ function node_menu() {
     'access arguments' => array('update', 1),
     'weight' => 0,
     'type' => MENU_LOCAL_TASK,
-    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+    'context' => MENU_CONTEXT_INLINE,
     'file' => 'node.pages.inc',
   );
   $items['node/%node/delete'] = array(
@@ -1791,6 +1791,7 @@ function node_menu() {
     'access arguments' => array(1),
     'weight' => 2,
     'type' => MENU_LOCAL_TASK,
+    'context' => MENU_CONTEXT_INLINE,
     'file' => 'node.pages.inc',
   );
   $items['node/%node/revisions/%node_revision/view'] = array(
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 223af2e..72ceea5 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1999,7 +1999,7 @@ function system_library_info() {
     'website' => 'http://underscorejs.org/',
     'version' => '1.4.0',
     'js' => array(
-      'core/misc/underscore/underscore.js' => array('group' => JS_LIBRARY),
+      'core/misc/underscore/underscore.js' => array('group' => JS_LIBRARY, 'weight' => -20),
     ),
   );
 
@@ -2009,7 +2009,7 @@ function system_library_info() {
     'website' => 'http://backbonejs.org/',
     'version' => '0.9.2',
     'js' => array(
-      'core/misc/backbone/backbone.js' => array('group' => JS_LIBRARY),
+      'core/misc/backbone/backbone.js' => array('group' => JS_LIBRARY, 'weight' => -19),
     ),
     'dependencies' => array(
       array('system', 'underscore'),
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
index 93cc8d4..9f65f1c 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
@@ -12,6 +12,13 @@
  */
 class TermTest extends TaxonomyTestBase {
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('taxonomy', 'contextual');
+
   public static function getInfo() {
     return array(
       'name' => 'Taxonomy term functions and forms',
@@ -22,7 +29,7 @@ public static function getInfo() {
 
   function setUp() {
     parent::setUp();
-    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access', 'access contextual links'));
     $this->drupalLogin($this->admin_user);
     $this->vocabulary = $this->createVocabulary();
 
