diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php
index bc6ae1a0d08131d5b1d8f77d4acadc2cf0bf7bea..c55927fb104e4bcf315eb0c607f88677b8d1619f 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleContentTest.php
@@ -212,6 +212,7 @@ function testContentTypeDirLang() {
    *  Test filtering Node content by language.
    */
   function testNodeAdminLanguageFilter() {
+    module_enable(array('views'));
     // User to add and remove language.
     $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'access content overview', 'administer nodes', 'bypass node access'));
 
@@ -227,14 +228,8 @@ function testNodeAdminLanguageFilter() {
     $node_en = $this->drupalCreateNode(array('langcode' => 'en'));
     $node_zh_hant = $this->drupalCreateNode(array('langcode' => 'zh-hant'));
 
-    $this->drupalGet('admin/content');
-
     // Verify filtering by language.
-    $edit = array(
-      'langcode' => 'zh-hant',
-    );
-    $this->drupalPost(NULL, $edit, t('Filter'));
-
+    $this->drupalGet('admin/content', array('query' => array('langcode' => 'zh-hant')));
     $this->assertLinkByHref('node/' . $node_zh_hant->nid . '/edit');
     $this->assertNoLinkByHref('node/' . $node_en->nid . '/edit');
   }
diff --git a/core/modules/node/config/views.view.content.yml b/core/modules/node/config/views.view.content.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a30f1cd511f9050b2dd408db51f061bbba125e20
--- /dev/null
+++ b/core/modules/node/config/views.view.content.yml
@@ -0,0 +1,359 @@
+base_field: nid
+base_table: node
+core: 8.x
+description: 'Find and manage content.'
+status: '1'
+display:
+  default:
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'access content overview'
+      cache:
+        type: none
+      query:
+        type: views_query
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Filter
+          reset_button: '0'
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: '1'
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: full
+        options:
+          items_per_page: '50'
+      style:
+        type: table
+        options:
+          grouping: {  }
+          row_class: ''
+          default_row_class: '1'
+          row_class_special: '1'
+          override: '1'
+          sticky: '1'
+          summary: ''
+          columns:
+            node_bulk_form: node_bulk_form
+            title: title
+            type: type
+            name: name
+            status: status
+            changed: changed
+            edit_node: edit_node
+            delete_node: delete_node
+            translation_link: translation_link
+            dropbutton: dropbutton
+          info:
+            node_bulk_form:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: ''
+            title:
+              sortable: '1'
+              default_sort_order: asc
+            type:
+              sortable: '1'
+              default_sort_order: asc
+            name:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: priority-low
+            status:
+              sortable: '1'
+              default_sort_order: asc
+              responsive: ''
+            changed:
+              sortable: '1'
+              default_sort_order: desc
+              responsive: priority-low
+            edit_node:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: ''
+            delete_node:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: ''
+            translation_link:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: ''
+            dropbutton:
+              sortable: '0'
+              default_sort_order: asc
+              responsive: ''
+          default: changed
+          empty_table: '1'
+      row:
+        type: fields
+      fields:
+        node_bulk_form:
+          id: node_bulk_form
+          table: node
+          field: node_bulk_form
+          label: ''
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          plugin_id: node_bulk_form
+        title:
+          id: title
+          table: node_field_data
+          field: title
+          label: Title
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          link_to_node: '1'
+          plugin_id: node
+        type:
+          id: type
+          table: node_field_data
+          field: type
+          label: 'Content Type'
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          link_to_node: '0'
+          machine_name: '0'
+          plugin_id: node_type
+        name:
+          id: name
+          table: users
+          field: name
+          relationship: uid
+          label: Author
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          link_to_user: '1'
+          overwrite_anonymous: '0'
+          anonymous_text: ''
+          format_username: '1'
+          plugin_id: user_name
+        status:
+          id: status
+          table: node_field_data
+          field: status
+          label: Status
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          type: published-notpublished
+          type_custom_true: ''
+          type_custom_false: ''
+          not: '0'
+          plugin_id: boolean
+        changed:
+          id: changed
+          table: node_field_data
+          field: changed
+          label: Updated
+          exclude: '0'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_empty: '0'
+          empty_zero: '0'
+          hide_alter_empty: '1'
+          date_format: short
+          custom_date_format: ''
+          timezone: ''
+          plugin_id: date
+        edit_node:
+          id: edit_node
+          table: views_entity_node
+          field: edit_node
+          label: ''
+          exclude: '1'
+          text: Edit
+          plugin_id: node_link_edit
+        delete_node:
+          id: delete_node
+          table: views_entity_node
+          field: delete_node
+          label: ''
+          exclude: '1'
+          text: Delete
+          plugin_id: node_link_delete
+        translation_link:
+          id: translation_link
+          table: node
+          field: translation_link
+          label: ''
+          exclude: '1'
+          alter:
+            alter_text: '0'
+          element_class: ''
+          element_default_classes: '1'
+          hide_alter_empty: '1'
+          hide_empty: '0'
+          empty_zero: '0'
+          empty: ''
+          text: Translate
+          optional: '1'
+          plugin_id: translation_entity_link
+        dropbutton:
+          id: dropbutton
+          table: views
+          field: dropbutton
+          label: Operations
+          fields:
+            edit_node: edit_node
+            delete_node: delete_node
+            translation_link: translation_link
+          destination: '1'
+          plugin_id: dropbutton
+      filters:
+        status_extra:
+          id: status_extra
+          table: node_field_data
+          field: status_extra
+          operator: '='
+          value: ''
+          plugin_id: node_status
+        status:
+          id: status
+          table: node_field_data
+          field: status
+          operator: '='
+          value: All
+          exposed: '1'
+          expose:
+            operator_id: ''
+            label: Status
+            description: ''
+            use_operator: '0'
+            operator: status_op
+            identifier: status
+            required: '0'
+            remember: '0'
+            multiple: '0'
+            remember_roles:
+              authenticated: authenticated
+          plugin_id: boolean
+        type:
+          id: type
+          table: node_field_data
+          field: type
+          operator: in
+          value: {  }
+          exposed: '1'
+          expose:
+            operator_id: type_op
+            label: Type
+            description: ''
+            use_operator: '0'
+            operator: type_op
+            identifier: type
+            required: '0'
+            remember: '0'
+            multiple: '0'
+            remember_roles:
+              authenticated: authenticated
+            reduce: '0'
+          plugin_id: bundle
+        langcode:
+          id: langcode
+          table: node
+          field: langcode
+          operator: in
+          value: {  }
+          group: '1'
+          exposed: '1'
+          expose:
+            operator_id: langcode_op
+            label: Language
+            operator: langcode_op
+            identifier: langcode
+            remember_roles:
+              authenticated: authenticated
+          optional: '1'
+          plugin_id: language
+      sorts: {  }
+      title: Content
+      empty:
+        area_text_custom:
+          id: area_text_custom
+          table: views
+          field: area_text_custom
+          empty: '1'
+          content: 'No content available.'
+          plugin_id: text_custom
+      arguments: {  }
+      relationships:
+        uid:
+          id: uid
+          table: node_field_data
+          field: uid
+          admin_label: author
+          required: '1'
+          plugin_id: standard
+      show_admin_links: '0'
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: '0'
+  page_1:
+    display_options:
+      path: admin/content/node
+      menu:
+        type: 'default tab'
+        title: Content
+        description: ''
+        name: admin
+        weight: '-10'
+        context: '0'
+      tab_options:
+        type: normal
+        title: Content
+        description: 'Find and manage content'
+        name: admin
+        weight: '-10'
+    display_plugin: page
+    display_title: Page
+    id: page_1
+    position: '1'
+label: Content
+module: node
+id: content
+tag: default
+langcode: en
diff --git a/core/modules/node/lib/Drupal/node/NodeBCDecorator.php b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php
new file mode 100644
index 0000000000000000000000000000000000000000..9fa6bbfb776d4fe879fec36e4357193d90cd28b3
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\NodeBCDecorator.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityBCDecorator;
+
+/**
+ * Defines the node specific entity BC decorator.
+ */
+class NodeBCDecorator extends EntityBCDecorator implements NodeInterface {
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index 55031006d24166f9842daa6758c3d410f8bd50c2..eb047c1094f88f9e3db096989cfc1401753fc4df 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\node\NodeInterface;
+use Drupal\node\NodeBCDecorator;
 
 /**
  * Defines the node entity class.
@@ -235,4 +236,15 @@ public function getRevisionId() {
     return $this->get('vid')->value;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getBCEntity() {
+    if (!isset($this->bcEntity)) {
+      $this->getPropertyDefinitions();
+      $this->bcEntity = new NodeBCDecorator($this, $this->fieldDefinitions);
+    }
+    return $this->bcEntity;
+  }
+
 }
diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php b/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..da633d710dce9c231befa919e2d72b857258d5a7
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/views/field/NodeBulkForm.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Plugin\views\field\NodeBulkForm.
+ */
+
+namespace Drupal\node\Plugin\views\field;
+
+use Drupal\Component\Annotation\PluginID;
+use Drupal\system\Plugin\views\field\BulkFormBase;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\EntityManager;
+
+/**
+ * Defines a node operations bulk form element.
+ *
+ * @PluginID("node_bulk_form")
+ */
+class NodeBulkForm extends BulkFormBase {
+
+  /**
+   * Constructs a new NodeBulkForm object.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManager $manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $manager);
+
+    // Filter the actions to only include those for the 'node' entity type.
+    $this->actions = array_filter($this->actions, function ($action) {
+      return $action->getType() == 'node';
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getBulkOptions() {
+    return array_map(function ($action) {
+      return $action->label();
+    }, $this->actions);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function views_form_validate(&$form, &$form_state) {
+    $selected = array_filter($form_state['values'][$this->options['id']]);
+    if (empty($selected)) {
+      form_set_error('', t('No items selected.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function views_form_submit(&$form, &$form_state) {
+    parent::views_form_submit($form, $form_state);
+    if ($form_state['step'] == 'views_form_views_form') {
+      Cache::invalidateTags(array('content' => TRUE));
+    }
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
index e34f61b438186206586ffb03491145f451704e91..c96c68cfca25fe7736cdd7212d39090a375969fb 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
@@ -12,6 +12,13 @@
  */
 class NodeAdminTest extends NodeTestBase {
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('views');
+
   public static function getInfo() {
     return array(
       'name' => 'Node administration',
@@ -39,38 +46,42 @@ function setUp() {
    */
   function testContentAdminSort() {
     $this->drupalLogin($this->admin_user);
+
+    // Create nodes that have different node.changed values.
+    $this->container->get('state')->set('node_test.storage_controller', TRUE);
+    module_enable(array('node_test'));
+    $changed = REQUEST_TIME;
     foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
-      $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6)));
+      $changed += 1000;
+      $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6), 'changed' => $changed));
     }
 
     // Test that the default sort by node.changed DESC actually fires properly.
     $nodes_query = db_select('node_field_data', 'n')
-      ->fields('n', array('nid'))
+      ->fields('n', array('title'))
       ->orderBy('changed', 'DESC')
       ->execute()
       ->fetchCol();
 
-    $nodes_form = array();
     $this->drupalGet('admin/content');
-    foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
-      $nodes_form[] = $input;
+    foreach ($nodes_query as $delta => $string) {
+      $elements = $this->xpath('//table[contains(@class, :class)]//tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string));
+      $this->assertTrue(!empty($elements), 'The node was found in the correct order.');
     }
-    $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form according to the default query.');
 
     // Compare the rendered HTML node list to a query for the nodes ordered by
     // title to account for possible database-dependent sort order.
     $nodes_query = db_select('node_field_data', 'n')
-      ->fields('n', array('nid'))
+      ->fields('n', array('title'))
       ->orderBy('title')
       ->execute()
       ->fetchCol();
 
-    $nodes_form = array();
-    $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'Title')));
-    foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) {
-      $nodes_form[] = $input;
+    $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'title')));
+    foreach ($nodes_query as $delta => $string) {
+      $elements = $this->xpath('//table[contains(@class, :class)]//tr[' . ($delta + 1) . ']/td[2]/a[normalize-space(text())=:label]', array(':class' => 'views-table', ':label' => $string));
+      $this->assertTrue(!empty($elements), 'The node was found in the correct order.');
     }
-    $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form the same as they are in the query.');
   }
 
   /**
@@ -95,30 +106,17 @@ function testContentAdminPages() {
       $this->assertLinkByHref('node/' . $node->nid);
       $this->assertLinkByHref('node/' . $node->nid . '/edit');
       $this->assertLinkByHref('node/' . $node->nid . '/delete');
-      // Verify tableselect.
-      $this->assertFieldByName('nodes[' . $node->nid . ']', '', 'Tableselect found.');
     }
 
     // Verify filtering by publishing status.
-    $edit = array(
-      'status' => 'status-1',
-    );
-    $this->drupalPost(NULL, $edit, t('Filter'));
-
-    $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.');
+    $this->drupalGet('admin/content', array('query' => array('status' => TRUE)));
 
     $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
     $this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
     $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
 
     // Verify filtering by status and content type.
-    $edit = array(
-      'type' => 'page',
-    );
-    $this->drupalPost(NULL, $edit, t('Refine'));
-
-    $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), 'Content list is filtered by status.');
-    $this->assertRaw(t('and where %property is %value', array('%property' => t('type'), '%value' => 'Basic page')), 'Content list is filtered by content type.');
+    $this->drupalGet('admin/content', array('query' => array('status' => TRUE, 'type' => 'page')));
 
     $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
     $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
diff --git a/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..838c553d2cadd3a550d4263e074dbcfa7c6518f5
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Views\BulkFormTest.
+ */
+
+namespace Drupal\node\Tests\Views;
+
+/**
+ * Tests the views bulk form test.
+ *
+ * @see \Drupal\node\Plugin\views\field\BulkForm
+ */
+class BulkFormTest extends NodeTestBase {
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = array('test_node_bulk_form');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Node: Bulk form',
+      'description' => 'Tests a node bulk form.',
+      'group' => 'Views Modules',
+    );
+  }
+
+  /**
+   * Tests the node bulk form.
+   */
+  public function testBulkForm() {
+    $this->drupalLogin($this->drupalCreateUser(array('administer nodes')));
+    $node = $this->drupalCreateNode();
+
+    $this->drupalGet('test-node-bulk-form');
+    $elements = $this->xpath('//select[@id="edit-action"]//option');
+    $this->assertIdentical(count($elements), 8, 'All node operations are found.');
+
+    // Block a node using the bulk form.
+    $this->assertTrue($node->status);
+    $edit = array(
+      'node_bulk_form[0]' => TRUE,
+      'action' => 'node_unpublish_action',
+    );
+    $this->drupalPost(NULL, $edit, t('Apply'));
+    // Re-load the node and check their status.
+    $node = entity_load('node', $node->id());
+    $this->assertFalse($node->status);
+  }
+
+}
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index 47ff29311a775fca65e5d72f6d8ce7edb7c5d3a4..821f41a537d0ec910c771ef29bf256597c7eaadf 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -5,8 +5,8 @@
  * Content administration and module settings user interface.
  */
 
-use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Language\Language;
+use Drupal\node\NodeInterface;
 
 /**
  * Page callback: Form constructor for the permission rebuild confirmation form.
@@ -31,197 +31,6 @@ function node_configure_rebuild_confirm_submit($form, &$form_state) {
   $form_state['redirect'] = 'admin/reports/status';
 }
 
-/**
- * Lists node administration filters that can be applied.
- *
- * @return
- *   An associative array of filters.
- */
-function node_filters() {
-  // Regular filters
-  $filters['status'] = array(
-    'title' => t('status'),
-    'options' => array(
-      '[any]' => t('any'),
-      'status-1' => t('published'),
-      'status-0' => t('not published'),
-      'promote-1' => t('promoted'),
-      'promote-0' => t('not promoted'),
-      'sticky-1' => t('sticky'),
-      'sticky-0' => t('not sticky'),
-    ),
-  );
-  // Include translation states if we have this module enabled
-  if (module_exists('translation')) {
-    $filters['status']['options'] += array(
-      'translate-0' => t('Up to date translation'),
-      'translate-1' => t('Outdated translation'),
-    );
-  }
-
-  $filters['type'] = array(
-    'title' => t('type'),
-    'options' => array(
-      '[any]' => t('any'),
-    ) + node_type_get_names(),
-  );
-
-  // Language filter if language support is present.
-  if (language_multilingual()) {
-    $languages = language_list(Language::STATE_ALL);
-    foreach ($languages as $langcode => $language) {
-      // Make locked languages appear special in the list.
-      $language_options[$langcode] = $language->locked ? t('- @name -', array('@name' => $language->name)) : $language->name;
-    }
-    $filters['langcode'] = array(
-      'title' => t('language'),
-      'options' => array(
-        '[any]' => t('- Any -'),
-      ) + $language_options,
-    );
-  }
-  return $filters;
-}
-
-/**
- * Applies filters for the node administration overview based on session.
- *
- * @param Drupal\Core\Database\Query\SelectInterface $query
- *   A SelectQuery to which the filters should be applied.
- */
-function node_build_filter_query(SelectInterface $query) {
-  // Build query
-  $filter_data = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
-  foreach ($filter_data as $index => $filter) {
-    list($key, $value) = $filter;
-    switch ($key) {
-      case 'status':
-        // Note: no exploitable hole as $key/$value have already been checked when submitted
-        list($key, $value) = explode('-', $value, 2);
-      case 'type':
-      case 'langcode':
-        $query->condition('n.' . $key, $value);
-        break;
-    }
-  }
-}
-
-/**
- * Returns the node administration filters form array to node_admin_content().
- *
- * @see node_admin_nodes()
- * @see node_admin_nodes_submit()
- * @see node_admin_nodes_validate()
- * @see node_filter_form_submit()
- * @see node_multiple_delete_confirm()
- * @see node_multiple_delete_confirm_submit()
- *
- * @ingroup forms
- */
-function node_filter_form() {
-  $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array();
-  $filters = node_filters();
-
-  $i = 0;
-  $form['filters'] = array(
-    '#type' => 'details',
-    '#title' => t('Show only items where'),
-    '#theme' => 'exposed_filters__node',
-  );
-  foreach ($session as $filter) {
-    list($type, $value) = $filter;
-    if ($type == 'term') {
-      // Load term name from DB rather than search and parse options array.
-      $value = module_invoke('taxonomy', 'term_load', $value);
-      $value = $value->name;
-    }
-    elseif ($type == 'langcode') {
-      $value = language_name($value);
-    }
-    else {
-      $value = $filters[$type]['options'][$value];
-    }
-    $t_args = array('%property' => $filters[$type]['title'], '%value' => $value);
-    if ($i++) {
-      $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $t_args));
-    }
-    else {
-      $form['filters']['current'][] = array('#markup' => t('where %property is %value', $t_args));
-    }
-    if (in_array($type, array('type', 'langcode'))) {
-      // Remove the option if it is already being filtered on.
-      unset($filters[$type]);
-    }
-  }
-
-  $form['filters']['status'] = array(
-    '#type' => 'container',
-    '#attributes' => array('class' => array('clearfix')),
-    '#prefix' => ($i ? '<div class="additional-filters">' . t('and where') . '</div>' : ''),
-  );
-  $form['filters']['status']['filters'] = array(
-    '#type' => 'container',
-    '#attributes' => array('class' => array('filters')),
-  );
-  foreach ($filters as $key => $filter) {
-    $form['filters']['status']['filters'][$key] = array(
-      '#type' => 'select',
-      '#options' => $filter['options'],
-      '#title' => $filter['title'],
-      '#default_value' => '[any]',
-    );
-  }
-
-  $form['filters']['status']['actions'] = array(
-    '#type' => 'actions',
-    '#attributes' => array('class' => array('container-inline')),
-  );
-  $form['filters']['status']['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => count($session) ? t('Refine') : t('Filter'),
-  );
-  if (count($session)) {
-    $form['filters']['status']['actions']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
-    $form['filters']['status']['actions']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
-  }
-
-  $form['#attached']['library'][] = array('system', 'drupal.form');
-
-  return $form;
-}
-
-/**
- * Form submission handler for node_filter_form().
- *
- * @see node_admin_content()
- * @see node_admin_nodes()
- * @see node_admin_nodes_submit()
- * @see node_admin_nodes_validate()
- * @see node_filter_form()
- * @see node_multiple_delete_confirm()
- * @see node_multiple_delete_confirm_submit()
- */
-function node_filter_form_submit($form, &$form_state) {
-  $filters = node_filters();
-  switch ($form_state['values']['op']) {
-    case t('Filter'):
-    case t('Refine'):
-      // Apply every filter that has a choice selected other than 'any'.
-      foreach ($filters as $filter => $options) {
-        if (isset($form_state['values'][$filter]) && $form_state['values'][$filter] != '[any]') {
-          $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]);
-        }
-      }
-      break;
-    case t('Undo'):
-      array_pop($_SESSION['node_overview_filter']);
-      break;
-    case t('Reset'):
-      $_SESSION['node_overview_filter'] = array();
-      break;
-  }
-}
-
 /**
  * Updates all nodes in the passed-in array with the passed-in field values.
  *
@@ -230,21 +39,24 @@ function node_filter_form_submit($form, &$form_state) {
  * work correctly.
  *
  * @param array $nodes
- *   Array of node nids to update.
+ *   Array of node nids or nodes to update.
  * @param array $updates
  *   Array of key/value pairs with node field names and the value to update that
  *   field to.
  * @param string $langcode
  *   (optional) The language updates should be applied to. If none is specified
  *   all available languages are processed.
+ * @param bool $load
+ *   (optional) TRUE if $nodes contains an array of node IDs to be loaded, FALSE
+ *   if it contains fully loaded nodes. Defaults to FALSE.
  */
-function node_mass_update($nodes, $updates, $langcode = NULL) {
+function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE) {
   // We use batch processing to prevent timeout when updating a large number
   // of nodes.
   if (count($nodes) > 10) {
     $batch = array(
       'operations' => array(
-        array('_node_mass_update_batch_process', array($nodes, $updates, $langcode))
+        array('_node_mass_update_batch_process', array($nodes, $updates, $langcode, $load))
       ),
       'finished' => '_node_mass_update_batch_finished',
       'title' => t('Processing'),
@@ -259,8 +71,11 @@ function node_mass_update($nodes, $updates, $langcode = NULL) {
     batch_set($batch);
   }
   else {
-    foreach ($nodes as $nid) {
-      _node_mass_update_helper($nid, $updates, $langcode);
+    if ($load) {
+      $nodes = entity_load_multiple('node', $nodes);
+    }
+    foreach ($nodes as $node) {
+      _node_mass_update_helper($node, $updates, $langcode);
     }
     drupal_set_message(t('The update has been performed.'));
   }
@@ -269,21 +84,20 @@ function node_mass_update($nodes, $updates, $langcode = NULL) {
 /**
  * Updates individual nodes when fewer than 10 are queued.
  *
- * @param $nid
- *   ID of node to update.
- * @param $updates
+ * @param \Drupal\node\NodeInterface $node
+ *   A node to update.
+ * @param array $updates
  *   Associative array of updates.
  * @param string $langcode
  *   (optional) The language updates should be applied to. If none is specified
  *   all available languages are processed.
  *
- * @return object
+ * @return \Drupal\node\NodeInterface
  *   An updated node object.
  *
  * @see node_mass_update()
  */
-function _node_mass_update_helper($nid, $updates, $langcode = NULL) {
-  $node = node_load($nid, TRUE);
+function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode = NULL) {
   $langcodes = isset($langcode) ? array($langcode) : array_keys($node->getTranslationLanguages());
   // For efficiency manually save the original node before applying any changes.
   $node->original = clone $node;
@@ -303,10 +117,13 @@ function _node_mass_update_helper($nid, $updates, $langcode = NULL) {
  *   An array of node IDs.
  * @param array $updates
  *   Associative array of updates.
+ * @param bool $load
+ *   TRUE if $nodes contains an array of node IDs to be loaded, FALSE if it
+ *   contains fully loaded nodes.
  * @param array $context
  *   An array of contextual key/values.
  */
-function _node_mass_update_batch_process($nodes, $updates, &$context) {
+function _node_mass_update_batch_process(array $nodes, array $updates, $load, array &$context) {
   if (!isset($context['sandbox']['progress'])) {
     $context['sandbox']['progress'] = 0;
     $context['sandbox']['max'] = count($nodes);
@@ -317,8 +134,11 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) {
   $count = min(5, count($context['sandbox']['nodes']));
   for ($i = 1; $i <= $count; $i++) {
     // For each nid, load the node, reset the values, and save it.
-    $nid = array_shift($context['sandbox']['nodes']);
-    $node = _node_mass_update_helper($nid, $updates);
+    $node = array_shift($context['sandbox']['nodes']);
+    if ($load) {
+      $node = entity_load('node', $node);
+    }
+    $node = _node_mass_update_helper($node, $updates);
 
     // Store result for post-processing in the finished callback.
     $context['results'][] = l($node->label(), 'node/' . $node->nid);
@@ -361,70 +181,12 @@ function _node_mass_update_batch_finished($success, $results, $operations) {
   }
 }
 
-/**
- * Page callback: Form constructor for the content administration form.
- *
- * @see node_admin_nodes()
- * @see node_admin_nodes_submit()
- * @see node_admin_nodes_validate()
- * @see node_filter_form()
- * @see node_filter_form_submit()
- * @see node_menu()
- * @see node_multiple_delete_confirm()
- * @see node_multiple_delete_confirm_submit()
- * @ingroup forms
- */
-function node_admin_content($form, $form_state) {
-  if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
-    return node_multiple_delete_confirm($form, $form_state, array_filter($form_state['values']['nodes']));
-  }
-  $form['filter'] = node_filter_form();
-  $form['#submit'][] = 'node_filter_form_submit';
-  $form['admin'] = node_admin_nodes();
-
-  return $form;
-}
-
 /**
  * Returns the admin form object to node_admin_content().
  *
- * @see node_admin_nodes_submit()
- * @see node_filter_form()
- * @see node_filter_form_submit()
- * @see node_multiple_delete_confirm()
- * @see node_multiple_delete_confirm_submit()
- *
  * @ingroup forms
  */
 function node_admin_nodes() {
-  $admin_access = user_access('administer nodes');
-
-  // Build the 'Update options' form.
-  $form['options'] = array(
-    '#type' => 'details',
-    '#title' => t('Update options'),
-    '#attributes' => array('class' => array('container-inline')),
-    '#access' => $admin_access,
-  );
-  $options = array();
-  $actions = entity_load_multiple_by_properties('action', array('type' => 'node'));
-  foreach ($actions as $id => $action) {
-    $options[$id] = $action->label();
-  }
-  $form['options']['operation'] = array(
-    '#type' => 'select',
-    '#title' => t('Action'),
-    '#title_display' => 'invisible',
-    '#options' => $options,
-    '#default_value' => 'approve',
-  );
-  $form['options']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Update'),
-    '#tableselect' => TRUE,
-    '#submit' => array('node_admin_nodes_submit'),
-  );
-
   // Enable language column and filter if multiple languages are enabled.
   $multilingual = language_multilingual();
 
@@ -462,7 +224,6 @@ function node_admin_nodes() {
   $query = db_select('node_field_data', 'n')
     ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
     ->extend('Drupal\Core\Database\Query\TableSortExtender');
-  node_build_filter_query($query);
 
   if (!user_access('bypass node access')) {
     // If the user is able to view their own unpublished nodes, allow them
@@ -573,95 +334,6 @@ function node_admin_nodes() {
     }
   }
 
-  // Only use a tableselect when the current user is able to perform any
-  // operations.
-  if ($admin_access) {
-    $form['nodes']['#tableselect'] = TRUE;
-  }
-
   $form['pager'] = array('#theme' => 'pager');
   return $form;
 }
-
-/**
- * Form submission handler for node_admin_nodes().
- *
- * Executes the chosen 'Update option' on the selected nodes.
- *
- * @see node_admin_nodes()
- * @see node_admin_nodes_validate()
- * @see node_filter_form()
- * @see node_filter_form_submit()
- * @see node_multiple_delete_confirm()
- * @see node_multiple_delete_confirm_submit()
- */
-function node_admin_nodes_submit($form, &$form_state) {
-  if ($action = entity_load('action', $form_state['values']['operation'])) {
-    $nodes = entity_load_multiple('node', array_filter($form_state['values']['nodes']));
-    $action->execute($nodes);
-    $operation_definition = $action->getPluginDefinition();
-    if (!empty($operation_definition['confirm_form_path'])) {
-      $form_state['redirect'] = $operation_definition['confirm_form_path'];
-    }
-    cache_invalidate_tags(array('content' => TRUE));
-  }
-  else {
-    // We need to rebuild the form to go to a second step. For example, to
-    // show the confirmation form for the deletion of nodes.
-    $form_state['rebuild'] = TRUE;
-  }
-}
-
-/**
- * Multiple node deletion confirmation form for node_admin_content().
- *
- * @see node_admin_nodes()
- * @see node_admin_nodes_submit()
- * @see node_admin_nodes_validate()
- * @see node_filter_form()
- * @see node_filter_form_submit()
- * @see node_multiple_delete_confirm_submit()
- * @ingroup forms
- */
-function node_multiple_delete_confirm($form, &$form_state, $nodes) {
-  $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
-  $node_entities = node_load_multiple(array_keys($nodes));
-  // array_filter returns only elements with TRUE values
-  foreach ($nodes as $nid => $value) {
-    $form['nodes'][$nid] = array(
-      '#type' => 'hidden',
-      '#value' => $nid,
-      '#prefix' => '<li>',
-      '#suffix' => check_plain($node_entities[$nid]->label()) . "</li>\n",
-    );
-  }
-  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
-  $form['#submit'][] = 'node_multiple_delete_confirm_submit';
-  $confirm_question = format_plural(count($nodes),
-                                  'Are you sure you want to delete this item?',
-                                  'Are you sure you want to delete these items?');
-  return confirm_form($form,
-                    $confirm_question,
-                    'admin/content', t('This action cannot be undone.'),
-                    t('Delete'), t('Cancel'));
-}
-
-/**
- * Form submission handler for node_multiple_delete_confirm().
- *
- * @see node_admin_nodes()
- * @see node_admin_nodes_submit()
- * @see node_admin_nodes_validate()
- * @see node_filter_form()
- * @see node_filter_form_submit()
- * @see node_multiple_delete_confirm()
- */
-function node_multiple_delete_confirm_submit($form, &$form_state) {
-  if ($form_state['values']['confirm']) {
-    entity_delete_multiple('node', array_keys($form_state['values']['nodes']));
-    $count = count($form_state['values']['nodes']);
-    watchdog('content', 'Deleted @count posts.', array('@count' => $count));
-    drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
-  }
-  $form_state['redirect'] = 'admin/content';
-}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 73f5dcade5ae7a8a20985cf52f7478baaa0816eb..c297fd90a7466d1563de63d009c62361a8f6799d 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1407,7 +1407,7 @@ function node_user_cancel($edit, $account, $method) {
         ->condition('uid', $account->uid)
         ->execute()
         ->fetchCol();
-      node_mass_update($nodes, array('status' => 0));
+      node_mass_update($nodes, array('status' => 0), NULL, TRUE);
       break;
 
     case 'user_cancel_reassign':
@@ -1419,7 +1419,7 @@ function node_user_cancel($edit, $account, $method) {
         ->condition('uid', $account->uid)
         ->execute()
         ->fetchCol();
-      node_mass_update($nodes, array('uid' => 0));
+      node_mass_update($nodes, array('uid' => 0), NULL, TRUE);
       // Anonymize old revisions.
       db_update('node_field_revision')
         ->fields(array('uid' => 0))
@@ -1595,8 +1595,7 @@ function node_menu() {
   $items['admin/content'] = array(
     'title' => 'Content',
     'description' => 'Find and manage content.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_admin_content'),
+    'page callback' => 'node_admin_nodes',
     'access arguments' => array('access content overview'),
     'weight' => -10,
     'file' => 'node.admin.inc',
diff --git a/core/modules/node/node.views.inc b/core/modules/node/node.views.inc
index ac427313f323c937010f7d7f0280972106d39ccb..52608b72f895bbdcd420ed29d1298965fbd5bb55 100644
--- a/core/modules/node/node.views.inc
+++ b/core/modules/node/node.views.inc
@@ -227,6 +227,14 @@ function node_views_data() {
     );
   }
 
+  $data['node']['node_bulk_form'] = array(
+    'title' => t('Node operations bulk form'),
+    'help' => t('Add a form element that lets you run operations on multiple nodes.'),
+    'field' => array(
+      'id' => 'node_bulk_form',
+    ),
+  );
+
   // Define some fields based upon views_handler_field_entity in the entity
   // table so they can be re-used with other query backends.
   // @see views_handler_field_entity
diff --git a/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTestStorageController.php b/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTestStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e58980709930d82e002c6b7674c6d061d7feeff
--- /dev/null
+++ b/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTestStorageController.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node_test\NodeTestStorageController.
+ */
+
+namespace Drupal\node_test;
+
+use Drupal\node\NodeStorageController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a test storage controller for nodes.
+ */
+class NodeTestStorageController extends NodeStorageController {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function preSave(EntityInterface $node) {
+    // Allow test nodes to specify their updated ('changed') time.
+  }
+
+}
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index c2ff3b40aea10a9e5771f42f74597da8bf892a0c..e65f9bcd7d9aeffbb0d3989d173da54b408e7f01 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -180,3 +180,12 @@ function node_test_node_insert(EntityInterface $node) {
     $node->save();
   }
 }
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function node_test_entity_info_alter(&$entity_info) {
+  if (Drupal::state()->get('node_test.storage_controller')) {
+    $entity_info['node']['controllers']['storage'] = 'Drupal\node_test\NodeTestStorageController';
+  }
+}
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5d998e98e2b253bc6c7c063e952091b07ea2f0f
--- /dev/null
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
@@ -0,0 +1,45 @@
+base_field: nid
+base_table: node
+core: 8.x
+description: ''
+status: '1'
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: ''
+    display_options:
+      style:
+        type: table
+      row:
+        type: fields
+      fields:
+        node_bulk_form:
+          id: node_bulk_form
+          table: node
+          field: node_bulk_form
+          plugin_id: node_bulk_form
+        title:
+          id: title
+          table: node_field_data
+          field: title
+          plugin_id: node
+      sorts:
+        nid:
+          id: nid
+          table: node
+          field: nid
+          order: ASC
+          plugin_id: standard
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_title: Page
+    position: ''
+    display_options:
+      path: test-node-bulk-form
+label: ''
+module: views
+id: test_node_bulk_form
+tag: ''
diff --git a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
index 348085ca014aa1f66dc557f1c12578b6b1958b58..4bc26dd76483aab77e5637ddcead55e828684495 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkFormBase.php
@@ -152,7 +152,7 @@ public function views_form_submit(&$form, &$form_state) {
 
       $operation_definition = $action->getPluginDefinition();
       if (!empty($operation_definition['confirm_form_path'])) {
-        $form_state['confirm_form_path'] = $operation_definition['confirm_form_path'];
+        $form_state['redirect'] = $operation_definition['confirm_form_path'];
       }
     }
   }
diff --git a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
index d2d992cb5cbb3c8425ae92cc614c7200f0fac145..b68b7220ccf67761563f83bb1bc293d77aa8bcc0 100644
--- a/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
+++ b/core/modules/tracker/lib/Drupal/tracker/Tests/TrackerTest.php
@@ -257,6 +257,7 @@ function testTrackerCronIndexing() {
    * Tests that publish/unpublish works at admin/content/node.
    */
   function testTrackerAdminUnpublish() {
+    module_enable(array('views'));
     $admin_user = $this->drupalCreateUser(array('access content overview', 'administer nodes', 'bypass node access'));
     $this->drupalLogin($admin_user);
 
@@ -271,10 +272,10 @@ function testTrackerAdminUnpublish() {
 
     // Unpublish the node and ensure that it's no longer displayed.
     $edit = array(
-      'operation' => 'node_unpublish_action',
-      'nodes[' . $node->nid . ']' => $node->nid,
+      'action' => 'node_unpublish_action',
+      'node_bulk_form[0]' => $node->nid,
     );
-    $this->drupalPost('admin/content', $edit, t('Update'));
+    $this->drupalPost('admin/content', $edit, t('Apply'));
 
     $this->drupalGet('tracker');
     $this->assertText(t('No content available.'), 'Node is displayed on the tracker listing pages.');
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index ae61b93a2840f82327c339ca586822819b425193..2f337d94bdcc9c98dde6537d1b8c7e053b89d007 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -124,7 +124,7 @@ function hook_user_cancel($edit, $account, $method) {
         ->condition('uid', $account->uid)
         ->execute()
         ->fetchCol();
-      node_mass_update($nodes, array('status' => 0));
+      node_mass_update($nodes, array('status' => 0), NULL, TRUE);
       break;
 
     case 'user_cancel_reassign':
@@ -135,7 +135,7 @@ function hook_user_cancel($edit, $account, $method) {
         ->condition('uid', $account->uid)
         ->execute()
         ->fetchCol();
-      node_mass_update($nodes, array('uid' => 0));
+      node_mass_update($nodes, array('uid' => 0), NULL, TRUE);
       // Anonymize old revisions.
       db_update('node_field_revision')
         ->fields(array('uid' => 0))