diff --git a/DEPS b/DEPS
index cf2bb68..25becbc 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '921ebe5b736b068182ce5de1ab0e36add06d5e2c',
+  'skia_revision': 'a00f34774712598cdee89b6a6fbdedb68cb9c193',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'fe9bb7e6e251159852770160cfb21dad3cf03523',
+  'v8_revision': '90043159789d2ea05da752676ce67b8c490d67e5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/chrome/VERSION b/chrome/VERSION
index e58a836..b1c05cb2 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=59
 MINOR=0
-BUILD=3069
+BUILD=3070
 PATCH=0
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 10f3fced..59164e3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1606,6 +1606,7 @@
     "//printing/features",
     "//rlz/features",
     "//services/data_decoder/public/cpp",
+    "//services/device/public/cpp:device_features",
     "//services/identity:lib",
     "//services/preferences/public/cpp",
     "//services/preferences/public/cpp:service_main",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index acdc6b4..ca33be37 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -48,6 +48,7 @@
   "+rlz",
   "+sandbox/win/src",  # The path doesn't say it, but this is the Windows sandbox.
   "+services/data_decoder/public/cpp",
+  "+services/device/public/cpp/device_features.h",
   "+services/identity/public/interfaces",
   "+services/preferences/public/cpp",
   "+services/preferences/public/interfaces",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index aa20d19..85391be 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -87,6 +87,7 @@
 #include "net/cert/cert_verify_proc_android.h"
 #include "ppapi/features/features.h"
 #include "printing/features/features.h"
+#include "services/device/public/cpp/device_features.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/compositor/compositor_switches.h"
 #include "ui/display/display_switches.h"
diff --git a/chrome/browser/android/vr_shell/BUILD.gn b/chrome/browser/android/vr_shell/BUILD.gn
index d73399c..07ef81d 100644
--- a/chrome/browser/android/vr_shell/BUILD.gn
+++ b/chrome/browser/android/vr_shell/BUILD.gn
@@ -20,6 +20,8 @@
       "animation.h",
       "easing.cc",
       "easing.h",
+      "fps_meter.cc",
+      "fps_meter.h",
       "gltf_asset.cc",
       "gltf_asset.h",
       "gltf_parser.cc",
@@ -107,6 +109,7 @@
 if (enable_vr) {
   test("vr_shell_unittests") {
     sources = [
+      "fps_meter_unittest.cc",
       "gltf_parser_unittest.cc",
       "test/paths.cc",
       "test/paths.h",
diff --git a/chrome/browser/android/vr_shell/fps_meter.cc b/chrome/browser/android/vr_shell/fps_meter.cc
new file mode 100644
index 0000000..09319b1
--- /dev/null
+++ b/chrome/browser/android/vr_shell/fps_meter.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/fps_meter.h"
+
+#include <algorithm>
+
+namespace vr_shell {
+
+namespace {
+
+static constexpr size_t kNumFrameTimes = 200;
+
+}  // namepsace
+
+FPSMeter::FPSMeter() : total_time_us_(0) {
+  frame_times_.reserve(kNumFrameTimes);
+}
+
+FPSMeter::~FPSMeter() {}
+
+void FPSMeter::AddFrame(const base::TimeTicks& time_stamp) {
+  if (last_time_stamp_.is_null()) {
+    last_time_stamp_ = time_stamp;
+    return;
+  }
+
+  base::TimeDelta delta = time_stamp - last_time_stamp_;
+  last_time_stamp_ = time_stamp;
+
+  total_time_us_ += delta.InMicroseconds();
+
+  if (frame_times_.size() + 1 < kNumFrameTimes) {
+    frame_times_.push_back(delta);
+  } else {
+    total_time_us_ -= frame_times_[current_index_].InMicroseconds();
+    frame_times_[current_index_] = delta;
+  }
+
+  current_index_++;
+  if (current_index_ >= kNumFrameTimes)
+    current_index_ = 0;
+}
+
+bool FPSMeter::CanComputeFPS() const {
+  return !frame_times_.empty();
+}
+
+// Simply takes the average time delta.
+double FPSMeter::GetFPS() const {
+  if (!CanComputeFPS())
+    return 0.0;
+
+  return (frame_times_.size() * 1.0e6) / total_time_us_;
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/fps_meter.h b/chrome/browser/android/vr_shell/fps_meter.h
new file mode 100644
index 0000000..8c736d0
--- /dev/null
+++ b/chrome/browser/android/vr_shell/fps_meter.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_VR_SHELL_FPS_METER_H_
+#define CHROME_BROWSER_ANDROID_VR_SHELL_FPS_METER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace vr_shell {
+
+// Computes fps based on submitted frame times.
+class FPSMeter {
+ public:
+  FPSMeter();
+  ~FPSMeter();
+
+  void AddFrame(const base::TimeTicks& time_stamp);
+
+  bool CanComputeFPS() const;
+
+  double GetFPS() const;
+
+ private:
+  size_t current_index_;
+  int64_t total_time_us_;
+  base::TimeTicks last_time_stamp_;
+  std::vector<base::TimeDelta> frame_times_;
+  DISALLOW_COPY_AND_ASSIGN(FPSMeter);
+};
+
+}  // namespace vr_shell
+
+#endif  // CHROME_BROWSER_ANDROID_VR_SHELL_FPS_METER_H_
diff --git a/chrome/browser/android/vr_shell/fps_meter_unittest.cc b/chrome/browser/android/vr_shell/fps_meter_unittest.cc
new file mode 100644
index 0000000..f5a9c069
--- /dev/null
+++ b/chrome/browser/android/vr_shell/fps_meter_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr_shell/fps_meter.h"
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace vr_shell {
+
+namespace {
+
+static constexpr double kTolerance = 0.01;
+
+base::TimeTicks usToTicks(uint64_t us) {
+  return base::TimeTicks::FromInternalValue(us);
+}
+
+base::TimeDelta usToDelta(uint64_t us) {
+  return base::TimeDelta::FromInternalValue(us);
+}
+
+}  // namespace
+
+TEST(FPSMeter, GetFPSWithTooFewFrames) {
+  FPSMeter meter;
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  meter.AddFrame(usToTicks(16000));
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  meter.AddFrame(usToTicks(32000));
+  EXPECT_TRUE(meter.CanComputeFPS());
+  EXPECT_LT(0.0, meter.GetFPS());
+}
+
+TEST(FPSMeter, AccurateFPSWithManyFrames) {
+  FPSMeter meter;
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  base::TimeTicks now = usToTicks(1);
+  base::TimeDelta frame_time = usToDelta(16666);
+
+  meter.AddFrame(now);
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  for (int i = 0; i < 5; ++i) {
+    now += frame_time;
+    meter.AddFrame(now);
+    EXPECT_TRUE(meter.CanComputeFPS());
+    EXPECT_NEAR(60.0, meter.GetFPS(), kTolerance);
+  }
+}
+
+TEST(FPSMeter, AccurateFPSWithHigherFramerate) {
+  FPSMeter meter;
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  base::TimeTicks now = usToTicks(1);
+  base::TimeDelta frame_time = base::TimeDelta::FromSecondsD(1.0 / 90.0);
+
+  meter.AddFrame(now);
+  EXPECT_FALSE(meter.CanComputeFPS());
+  EXPECT_FLOAT_EQ(0.0, meter.GetFPS());
+
+  for (int i = 0; i < 5; ++i) {
+    now += frame_time;
+    meter.AddFrame(now);
+    EXPECT_TRUE(meter.CanComputeFPS());
+    EXPECT_NEAR(1.0 / frame_time.InSecondsF(), meter.GetFPS(), kTolerance);
+  }
+}
+
+}  // namespace vr_shell
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.cc b/chrome/browser/android/vr_shell/vr_shell_gl.cc
index a795a86f..77df7ca 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.cc
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/android/vr_shell/fps_meter.h"
 #include "chrome/browser/android/vr_shell/mailbox_to_surface_bridge.h"
 #include "chrome/browser/android/vr_shell/ui_elements.h"
 #include "chrome/browser/android/vr_shell/ui_interface.h"
@@ -163,6 +164,9 @@
       binding_(this),
       weak_vr_shell_(weak_vr_shell),
       main_thread_task_runner_(std::move(main_thread_task_runner)),
+#if DCHECK_IS_ON()
+      fps_meter_(new FPSMeter()),
+#endif
       weak_ptr_factory_(this) {
   GvrInit(gvr_api);
 }
@@ -875,6 +879,13 @@
     TRACE_EVENT0("gpu", "VrShellGl::SwapBuffers");
     surface_->SwapBuffers();
   }
+
+#if DCHECK_IS_ON()
+  // After saving the timestamp, fps will be available via GetFPS().
+  // TODO(vollick): enable rendering of this framerate in a HUD.
+  fps_meter_->AddFrame(current_time);
+  LOG(ERROR) << "fps: " << fps_meter_->GetFPS();
+#endif
 }
 
 void VrShellGl::DrawWorldElements(const vr::Mat4f& head_pose) {
diff --git a/chrome/browser/android/vr_shell/vr_shell_gl.h b/chrome/browser/android/vr_shell/vr_shell_gl.h
index 97dbddf..574bb82 100644
--- a/chrome/browser/android/vr_shell/vr_shell_gl.h
+++ b/chrome/browser/android/vr_shell/vr_shell_gl.h
@@ -44,6 +44,7 @@
 
 namespace vr_shell {
 
+class FPSMeter;
 class MailboxToSurfaceBridge;
 class UiScene;
 class VrController;
@@ -242,6 +243,8 @@
   // Attributes for gesture detection while holding app button.
   gfx::Vector3dF controller_start_direction_;
 
+  std::unique_ptr<FPSMeter> fps_meter_;
+
   base::WeakPtrFactory<VrShellGl> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(VrShellGl);
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc
index 10ced259..4847d9a 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -630,6 +630,7 @@
   migration_screen->SetContinueLoginCallback(base::BindOnce(
       &ExistingUserController::ContinuePerformLogin, weak_factory_.GetWeakPtr(),
       login_performer_->auth_mode()));
+  migration_screen->SetupInitialView();
 }
 
 void ExistingUserController::ShowTPMError() {
diff --git a/chrome/browser/chromeos/login/screens/encryption_migration_screen.cc b/chrome/browser/chromeos/login/screens/encryption_migration_screen.cc
index d29b647..211dc57 100644
--- a/chrome/browser/chromeos/login/screens/encryption_migration_screen.cc
+++ b/chrome/browser/chromeos/login/screens/encryption_migration_screen.cc
@@ -64,4 +64,9 @@
   view_->SetContinueLoginCallback(std::move(callback));
 }
 
+void EncryptionMigrationScreen::SetupInitialView() {
+  DCHECK(view_);
+  view_->SetupInitialView();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/encryption_migration_screen.h b/chrome/browser/chromeos/login/screens/encryption_migration_screen.h
index 9835c53..b4f95d0 100644
--- a/chrome/browser/chromeos/login/screens/encryption_migration_screen.h
+++ b/chrome/browser/chromeos/login/screens/encryption_migration_screen.h
@@ -41,6 +41,10 @@
   // session from the migration UI.
   void SetContinueLoginCallback(ContinueLoginCallback callback);
 
+  // Setup the initial view in the migration UI.
+  // This should be called after other state like UserContext, etc... are set.
+  void SetupInitialView();
+
  private:
   EncryptionMigrationScreenView* view_;
 
diff --git a/chrome/browser/chromeos/login/screens/encryption_migration_screen_view.h b/chrome/browser/chromeos/login/screens/encryption_migration_screen_view.h
index bd25ee6..cdd7dc79 100644
--- a/chrome/browser/chromeos/login/screens/encryption_migration_screen_view.h
+++ b/chrome/browser/chromeos/login/screens/encryption_migration_screen_view.h
@@ -39,6 +39,7 @@
   virtual void SetUserContext(const UserContext& user_context) = 0;
   virtual void SetShouldResume(bool should_resume) = 0;
   virtual void SetContinueLoginCallback(ContinueLoginCallback callback) = 0;
+  virtual void SetupInitialView() = 0;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/plugins/flash_permission_browsertest.cc b/chrome/browser/plugins/flash_permission_browsertest.cc
index 9c8d56c..65151612 100644
--- a/chrome/browser/plugins/flash_permission_browsertest.cc
+++ b/chrome/browser/plugins/flash_permission_browsertest.cc
@@ -73,6 +73,10 @@
       EXPECT_TRUE(reload_waiter.Wait());
     } else {
       EXPECT_TRUE(RunScriptReturnBool("triggerPrompt();"));
+      // Make a round trip to the renderer to flush any old did stop IPCs,
+      // otherwise they can race with the next navigation and cause it to be
+      // cancelled if it's the same URL.
+      EXPECT_TRUE(ExecuteScript(GetWebContents(), std::string()));
     }
   }
 
@@ -102,9 +106,7 @@
   CommonFailsIfDismissed();
 }
 
-// https://crbug.com/706780
-IN_PROC_BROWSER_TEST_F(FlashPermissionBrowserTest,
-                       DISABLED_CommonFailsIfBlocked) {
+IN_PROC_BROWSER_TEST_F(FlashPermissionBrowserTest, CommonFailsIfBlocked) {
   CommonFailsIfBlocked();
 }
 
@@ -206,8 +208,7 @@
   EXPECT_TRUE(FeatureUsageSucceeds());
 }
 
-// Failing often. http://crbug.com/706780.
-IN_PROC_BROWSER_TEST_F(FlashPermissionBrowserTest, DISABLED_BlockFileURL) {
+IN_PROC_BROWSER_TEST_F(FlashPermissionBrowserTest, BlockFileURL) {
   base::FilePath test_path;
   PathService::Get(chrome::DIR_TEST_DATA, &test_path);
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/resources/chromeos/login/encryption_migration.html b/chrome/browser/resources/chromeos/login/encryption_migration.html
index 6a76c3ee..b1d5d84 100644
--- a/chrome/browser/resources/chromeos/login/encryption_migration.html
+++ b/chrome/browser/resources/chromeos/login/encryption_migration.html
@@ -13,6 +13,11 @@
     <link rel="stylesheet" href="oobe_dialog_parameters.css">
     <template is="dom-if" if="[[isInitial_(uiState)]]">
       <oobe-dialog tabindex="0" has-buttons>
+        <p>Checking the system...</p>
+      </oobe-dialog>
+    </template>
+    <template is="dom-if" if="[[isReady_(uiState)]]">
+      <oobe-dialog tabindex="0" has-buttons>
         <div class="header">
           <h1 class="title">Your file system needs upgrade</h1>
           <div class="subtitle">
@@ -71,5 +76,26 @@
         </div>
       </oobe-dialog>
     </template>
+    <template is="dom-if" if="[[isNotEnoughSpace_(uiState)]]">
+      <oobe-dialog tabindex="0" has-buttons>
+        <div class="header">
+          <h1 class="title">Not enough storage for update</h1>
+          <div class="subtitle">
+            Please free up some space on your device and sign in again.
+          </div>
+        </div>
+        <div class="bottom-buttons flex layout horizontal">
+          <div class="flex"></div>
+          <template is="dom-if" if="[[!isResuming]]">
+            <oobe-text-button inverse on-tap="onSkip_">Continue
+            </oobe-text-button>
+          </template>
+          <template is="dom-if" if="[[isResuming]]">
+            <oobe-text-button inverse on-tap="onRestart_">Restart
+            </oobe-text-button>
+          </template>
+        </div>
+      </oobe-dialog>
+    </template>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/chromeos/login/encryption_migration.js b/chrome/browser/resources/chromeos/login/encryption_migration.js
index 86f8e43..f78d68b8 100644
--- a/chrome/browser/resources/chromeos/login/encryption_migration.js
+++ b/chrome/browser/resources/chromeos/login/encryption_migration.js
@@ -14,9 +14,11 @@
  */
 var EncryptionMigrationUIState = {
   INITIAL: 0,
-  MIGRATING: 1,
-  MIGRATION_SUCCEEDED: 2,
-  MIGRATION_FAILED: 3
+  READY: 1,
+  MIGRATING: 2,
+  MIGRATION_SUCCEEDED: 3,
+  MIGRATION_FAILED: 4,
+  NOT_ENOUGH_SPACE: 5
 };
 
 Polymer({
@@ -40,6 +42,14 @@
       type: Number,
       value: -1
     },
+
+    /**
+     * Whether the current migration is resuming the previous one.
+     */
+    isResuming: {
+      type: Boolean,
+      value: false
+    },
   },
 
   /**
@@ -52,6 +62,15 @@
   },
 
   /**
+   * Returns true if the migration is ready to start.
+   * @param {EncryptionMigrationUIState} state Current UI state
+   * @private
+   */
+  isReady_: function(state) {
+    return state == EncryptionMigrationUIState.READY;
+  },
+
+  /**
    * Returns true if the migration is in progress.
    * @param {EncryptionMigrationUIState} state Current UI state
    * @private
@@ -79,6 +98,15 @@
   },
 
   /**
+   * Returns true if the available space is not enough to start migration.
+   * @param {EncryptionMigrationUIState} state Current UI state
+   * @private
+   */
+  isNotEnoughSpace_: function(state) {
+    return state == EncryptionMigrationUIState.NOT_ENOUGH_SPACE;
+  },
+
+  /**
    * Returns true if the current migration progress is unknown.
    * @param {number} progress
    * @private
diff --git a/chrome/browser/resources/chromeos/login/screen_encryption_migration.js b/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
index d757568..ca8cc6d0 100644
--- a/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
+++ b/chrome/browser/resources/chromeos/login/screen_encryption_migration.js
@@ -8,6 +8,7 @@
     EXTERNAL_API: [
       'setUIState',
       'setMigrationProgress',
+      'setIsResuming',
     ],
 
     /**
@@ -57,5 +58,14 @@
     setMigrationProgress: function(progress) {
       $('encryption-migration-element').progress = progress;
     },
+
+    /**
+     * Updates the migration screen based on whether the migration process is
+     * resuming the previous one.
+     * @param {boolean} isResuming
+     */
+    setIsResuming: function(isResuming) {
+      $('encryption-migration-element').isResuming = isResuming;
+    },
   };
 });
diff --git a/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc
index 7302767..e864c38 100644
--- a/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.cc
@@ -7,6 +7,9 @@
 #include <string>
 #include <utility>
 
+#include "base/files/file_path.h"
+#include "base/sys_info.h"
+#include "base/task_scheduler/post_task.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chromeos/cryptohome/homedir_methods.h"
 #include "chromeos/dbus/cryptohome_client.h"
@@ -16,6 +19,12 @@
 
 constexpr char kJsScreenPath[] = "login.EncryptionMigrationScreen";
 
+// Path to the mount point to check the available space.
+constexpr char kCheckStoragePath[] = "/home";
+
+// The minimum size of available space to start the migration.
+constexpr int64_t kMinimumAvailableStorage = 10LL * 1024 * 1024;  // 10MB
+
 // JS API callbacks names.
 constexpr char kJsApiStartMigration[] = "startMigration";
 constexpr char kJsApiSkipMigration[] = "skipMigration";
@@ -59,10 +68,7 @@
 }
 
 void EncryptionMigrationScreenHandler::SetShouldResume(bool should_resume) {
-  if (current_ui_state_ == INITIAL && should_resume) {
-    // TODO(fukino): Wait until the battery gets enough level.
-    StartMigration();
-  }
+  should_resume_ = should_resume;
 }
 
 void EncryptionMigrationScreenHandler::SetContinueLoginCallback(
@@ -70,6 +76,10 @@
   continue_login_callback_ = std::move(callback);
 }
 
+void EncryptionMigrationScreenHandler::SetupInitialView() {
+  CheckAvailableStorage();
+}
+
 void EncryptionMigrationScreenHandler::DeclareLocalizedValues(
     ::login::LocalizedValuesBuilder* builder) {}
 
@@ -93,7 +103,6 @@
 }
 
 void EncryptionMigrationScreenHandler::HandleStartMigration() {
-  // TODO(fukino): Wait until the battery gets enough level.
   StartMigration();
 }
 
@@ -124,6 +133,31 @@
   CallJS("setUIState", static_cast<int>(state));
 }
 
+void EncryptionMigrationScreenHandler::CheckAvailableStorage() {
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      base::TaskTraits().MayBlock().WithPriority(
+          base::TaskPriority::USER_VISIBLE),
+      base::Bind(&base::SysInfo::AmountOfFreeDiskSpace,
+                 base::FilePath(kCheckStoragePath)),
+      base::Bind(&EncryptionMigrationScreenHandler::OnGetAvailableStorage,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void EncryptionMigrationScreenHandler::OnGetAvailableStorage(int64_t size) {
+  if (size < kMinimumAvailableStorage) {
+    UpdateUIState(NOT_ENOUGH_STORAGE);
+    CallJS("setIsResuming", should_resume_);
+  } else {
+    if (should_resume_) {
+      // TODO(fukino): Check the battery level.
+      StartMigration();
+    } else {
+      UpdateUIState(READY);
+    }
+  }
+}
+
 void EncryptionMigrationScreenHandler::StartMigration() {
   DBusThreadManager::Get()
       ->GetCryptohomeClient()
diff --git a/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h
index 719af81c..c2604b4 100644
--- a/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/encryption_migration_screen_handler.h
@@ -27,6 +27,7 @@
   void SetUserContext(const UserContext& user_context) override;
   void SetShouldResume(bool should_resume) override;
   void SetContinueLoginCallback(ContinueLoginCallback callback) override;
+  void SetupInitialView() override;
 
   // BaseScreenHandler implementation:
   void DeclareLocalizedValues(
@@ -38,9 +39,11 @@
   // EncryptionMigrationUIState in JS code.
   enum UIState {
     INITIAL = 0,
-    MIGRATING = 1,
-    MIGRATION_SUCCEEDED = 2,
-    MIGRATION_FAILED = 3
+    READY = 1,
+    MIGRATING = 2,
+    MIGRATION_SUCCEEDED = 3,
+    MIGRATION_FAILED = 4,
+    NOT_ENOUGH_STORAGE = 5,
   };
 
   // WebUIMessageHandler implementation:
@@ -55,6 +58,8 @@
   void UpdateUIState(UIState state);
 
   // Requests cryptohome to start encryption migration.
+  void CheckAvailableStorage();
+  void OnGetAvailableStorage(int64_t size);
   void StartMigration();
 
   // Handlers for cryptohome API callbacks.
@@ -76,6 +81,9 @@
   // The callback which is used to log in to the session from the migration UI.
   ContinueLoginCallback continue_login_callback_;
 
+  // True if the system should resume the previous incomplete migration.
+  bool should_resume_ = false;
+
   base::WeakPtrFactory<EncryptionMigrationScreenHandler> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(EncryptionMigrationScreenHandler);
diff --git a/chrome/tools/build/linux/FILES.cfg b/chrome/tools/build/linux/FILES.cfg
index aff7afd3..338e15f6 100644
--- a/chrome/tools/build/linux/FILES.cfg
+++ b/chrome/tools/build/linux/FILES.cfg
@@ -127,7 +127,7 @@
   # SwiftShader files:
   {
     'filename': 'swiftshader/libGLESv2.so',
-    'buildtype': ['dev, 'official''],
+    'buildtype': ['dev', 'official'],
   },
   {
     'filename': 'swiftshader/libEGL.so',
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 1478524..ffb13bf 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -514,6 +514,8 @@
     "cryptohome/mock_async_method_caller.h",
     "cryptohome/mock_homedir_methods.cc",
     "cryptohome/mock_homedir_methods.h",
+    "dbus/biod/test_utils.cc",
+    "dbus/biod/test_utils.h",
     "dbus/mock_cryptohome_client.cc",
     "dbus/mock_cryptohome_client.h",
     "dbus/mock_lorgnette_manager_client.cc",
@@ -613,6 +615,7 @@
     "cryptohome/homedir_methods_unittest.cc",
     "cryptohome/system_salt_getter_unittest.cc",
     "dbus/biod/biod_client_unittest.cc",
+    "dbus/biod/fake_biod_client_unittest.cc",
     "dbus/blocking_method_caller_unittest.cc",
     "dbus/cras_audio_client_unittest.cc",
     "dbus/cros_disks_client_unittest.cc",
diff --git a/chromeos/dbus/biod/biod_client_unittest.cc b/chromeos/dbus/biod/biod_client_unittest.cc
index 8bb2a90..60c7bf4 100644
--- a/chromeos/dbus/biod/biod_client_unittest.cc
+++ b/chromeos/dbus/biod/biod_client_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "chromeos/dbus/biod/test_utils.h"
 #include "dbus/mock_bus.h"
 #include "dbus/mock_object_proxy.h"
 #include "dbus/object_path.h"
@@ -34,36 +35,6 @@
 // determine when empty values have been assigned.
 const char kInvalidString[] = "invalidString";
 
-void CopyObjectPath(dbus::ObjectPath* dest_path,
-                    const dbus::ObjectPath& src_path) {
-  CHECK(dest_path);
-  *dest_path = src_path;
-}
-
-void CopyObjectPathArray(
-    std::vector<dbus::ObjectPath>* dest_object_paths,
-    const std::vector<dbus::ObjectPath>& src_object_paths) {
-  CHECK(dest_object_paths);
-  *dest_object_paths = src_object_paths;
-}
-
-void CopyString(std::string* dest_str, const std::string& src_str) {
-  CHECK(dest_str);
-  *dest_str = src_str;
-}
-
-void CopyDBusMethodCallStatus(DBusMethodCallStatus* dest_status,
-                              DBusMethodCallStatus src_status) {
-  CHECK(dest_status);
-  *dest_status = src_status;
-}
-
-void CopyBiometricType(biod::BiometricType* dest_type,
-                       biod::BiometricType src_type) {
-  CHECK(dest_type);
-  *dest_type = src_type;
-}
-
 // Matcher that verifies that a dbus::Message has member |name|.
 MATCHER_P(HasMember, name, "") {
   if (arg->GetMember() != name) {
@@ -80,39 +51,6 @@
   callback.Run(response.get());
 }
 
-// Implementation of BiodClient::Observer.
-class TestBiodObserver : public BiodClient::Observer {
- public:
-  TestBiodObserver() {}
-  ~TestBiodObserver() override {}
-
-  int num_enroll_scans_received() const { return num_enroll_scans_received_; }
-  int num_auth_scans_received() const { return num_auth_scans_received_; }
-  int num_failures_received() const { return num_failures_received_; }
-
-  // BiodClient::Observer:
-  void BiodServiceRestarted() override {}
-
-  void BiodEnrollScanDoneReceived(biod::ScanResult scan_result,
-                                  bool enroll_sesion_complete) override {
-    num_enroll_scans_received_++;
-  }
-
-  void BiodAuthScanDoneReceived(biod::ScanResult scan_result,
-                                const AuthScanMatches& matches) override {
-    num_auth_scans_received_++;
-  }
-
-  void BiodSessionFailedReceived() override { num_failures_received_++; }
-
- private:
-  int num_enroll_scans_received_ = 0;
-  int num_auth_scans_received_ = 0;
-  int num_failures_received_ = 0;
-
-  DISALLOW_COPY_AND_ASSIGN(TestBiodObserver);
-};
-
 }  // namespace
 
 class BiodClientTest : public testing::Test {
@@ -264,8 +202,9 @@
   AddMethodExpectation(biod::kBiometricsManagerStartEnrollSessionMethod,
                        std::move(response));
   dbus::ObjectPath returned_path(kInvalidTestPath);
-  client_->StartEnrollSession(kFakeId, kFakeLabel,
-                              base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartEnrollSession(
+      kFakeId, kFakeLabel,
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kFakeObjectPath, returned_path);
 
@@ -274,8 +213,9 @@
   AddMethodExpectation(biod::kBiometricsManagerStartEnrollSessionMethod,
                        nullptr);
   returned_path = dbus::ObjectPath(kInvalidTestPath);
-  client_->StartEnrollSession(kFakeId, kFakeLabel,
-                              base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartEnrollSession(
+      kFakeId, kFakeLabel,
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(dbus::ObjectPath(), returned_path);
 
@@ -285,8 +225,9 @@
   AddMethodExpectation(biod::kBiometricsManagerStartEnrollSessionMethod,
                        std::move(bad_response));
   returned_path = dbus::ObjectPath(kInvalidTestPath);
-  client_->StartEnrollSession(kFakeId, kFakeLabel,
-                              base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartEnrollSession(
+      kFakeId, kFakeLabel,
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(dbus::ObjectPath(), returned_path);
 }
@@ -309,7 +250,8 @@
   std::vector<dbus::ObjectPath> returned_object_paths = {
       dbus::ObjectPath(kInvalidTestPath)};
   client_->GetRecordsForUser(
-      kFakeId, base::Bind(&CopyObjectPathArray, &returned_object_paths));
+      kFakeId,
+      base::Bind(&test_utils::CopyObjectPathArray, &returned_object_paths));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kFakeObjectPaths, returned_object_paths);
 
@@ -319,7 +261,8 @@
                        nullptr);
   returned_object_paths = {dbus::ObjectPath(kInvalidTestPath)};
   client_->GetRecordsForUser(
-      kFakeId, base::Bind(&CopyObjectPathArray, &returned_object_paths));
+      kFakeId,
+      base::Bind(&test_utils::CopyObjectPathArray, &returned_object_paths));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(std::vector<dbus::ObjectPath>(), returned_object_paths);
 }
@@ -333,7 +276,7 @@
                        std::move(response));
   DBusMethodCallStatus returned_status = static_cast<DBusMethodCallStatus>(-1);
   client_->DestroyAllRecords(
-      base::Bind(&CopyDBusMethodCallStatus, &returned_status));
+      base::Bind(&test_utils::CopyDBusMethodCallStatus, &returned_status));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(DBUS_METHOD_CALL_SUCCESS, returned_status);
 
@@ -342,7 +285,7 @@
                        nullptr);
   returned_status = static_cast<DBusMethodCallStatus>(-1);
   client_->DestroyAllRecords(
-      base::Bind(&CopyDBusMethodCallStatus, &returned_status));
+      base::Bind(&test_utils::CopyDBusMethodCallStatus, &returned_status));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(DBUS_METHOD_CALL_FAILURE, returned_status);
 }
@@ -359,7 +302,8 @@
   AddMethodExpectation(biod::kBiometricsManagerStartAuthSessionMethod,
                        std::move(response));
   dbus::ObjectPath returned_path(kInvalidTestPath);
-  client_->StartAuthSession(base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartAuthSession(
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kFakeObjectPath, returned_path);
 
@@ -367,7 +311,8 @@
   // response is an empty object path.
   AddMethodExpectation(biod::kBiometricsManagerStartAuthSessionMethod, nullptr);
   returned_path = dbus::ObjectPath(kInvalidTestPath);
-  client_->StartAuthSession(base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartAuthSession(
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(dbus::ObjectPath(), returned_path);
 
@@ -377,7 +322,8 @@
   AddMethodExpectation(biod::kBiometricsManagerStartAuthSessionMethod,
                        std::move(bad_response));
   returned_path = dbus::ObjectPath(kInvalidTestPath);
-  client_->StartAuthSession(base::Bind(&CopyObjectPath, &returned_path));
+  client_->StartAuthSession(
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(dbus::ObjectPath(), returned_path);
 }
@@ -395,7 +341,7 @@
   biod::BiometricType returned_biometric_type = biod::BIOMETRIC_TYPE_MAX;
   AddMethodExpectation(dbus::kDBusPropertiesGet, std::move(response));
   client_->RequestType(
-      base::Bind(&CopyBiometricType, &returned_biometric_type));
+      base::Bind(&test_utils::CopyBiometricType, &returned_biometric_type));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kFakeBiometricType, returned_biometric_type);
 
@@ -404,7 +350,7 @@
   returned_biometric_type = biod::BIOMETRIC_TYPE_MAX;
   AddMethodExpectation(dbus::kDBusPropertiesGet, nullptr);
   client_->RequestType(
-      base::Bind(&CopyBiometricType, &returned_biometric_type));
+      base::Bind(&test_utils::CopyBiometricType, &returned_biometric_type));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(biod::BiometricType::BIOMETRIC_TYPE_UNKNOWN,
             returned_biometric_type);
@@ -422,16 +368,16 @@
   // exact string.
   std::string returned_label = kInvalidString;
   AddMethodExpectation(dbus::kDBusPropertiesGet, std::move(response));
-  client_->RequestRecordLabel(kFakeRecordPath,
-                              base::Bind(&CopyString, &returned_label));
+  client_->RequestRecordLabel(
+      kFakeRecordPath, base::Bind(&test_utils::CopyString, &returned_label));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(kFakeLabel, returned_label);
 
   // Verify that by sending a null reponse, the result is an empty string.
   returned_label = kInvalidString;
   AddMethodExpectation(dbus::kDBusPropertiesGet, nullptr);
-  client_->RequestRecordLabel(kFakeRecordPath,
-                              base::Bind(&CopyString, &returned_label));
+  client_->RequestRecordLabel(
+      kFakeRecordPath, base::Bind(&test_utils::CopyString, &returned_label));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ("", returned_label);
 }
@@ -439,22 +385,22 @@
 // Verify when signals are mocked, an observer will catch the signals as
 // expected.
 TEST_F(BiodClientTest, TestNotifyObservers) {
-  TestBiodObserver observer;
+  test_utils::TestBiodObserver observer;
   client_->AddObserver(&observer);
   EXPECT_TRUE(client_->HasObserver(&observer));
 
   const biod::ScanResult scan_signal = biod::ScanResult::SCAN_RESULT_SUCCESS;
   const bool enroll_session_complete = false;
   const AuthScanMatches test_attempt;
-  EXPECT_EQ(0, observer.num_enroll_scans_received());
-  EXPECT_EQ(0, observer.num_auth_scans_received());
+  EXPECT_EQ(0, observer.NumEnrollScansReceived());
+  EXPECT_EQ(0, observer.NumAuthScansReceived());
   EXPECT_EQ(0, observer.num_failures_received());
 
   EmitEnrollScanDoneSignal(scan_signal, enroll_session_complete);
-  EXPECT_EQ(1, observer.num_enroll_scans_received());
+  EXPECT_EQ(1, observer.NumEnrollScansReceived());
 
   EmitAuthScanDoneSignal(scan_signal, test_attempt);
-  EXPECT_EQ(1, observer.num_auth_scans_received());
+  EXPECT_EQ(1, observer.NumAuthScansReceived());
 
   EmitScanFailedSignal();
   EXPECT_EQ(1, observer.num_failures_received());
@@ -463,8 +409,8 @@
 
   EmitEnrollScanDoneSignal(scan_signal, enroll_session_complete);
   EmitAuthScanDoneSignal(scan_signal, test_attempt);
-  EXPECT_EQ(1, observer.num_enroll_scans_received());
-  EXPECT_EQ(1, observer.num_auth_scans_received());
+  EXPECT_EQ(1, observer.NumEnrollScansReceived());
+  EXPECT_EQ(1, observer.NumAuthScansReceived());
   EXPECT_EQ(1, observer.num_failures_received());
 }
 }  // namespace chromeos
diff --git a/chromeos/dbus/biod/fake_biod_client.cc b/chromeos/dbus/biod/fake_biod_client.cc
index 6eab8ce..7ab9ccd 100644
--- a/chromeos/dbus/biod/fake_biod_client.cc
+++ b/chromeos/dbus/biod/fake_biod_client.cc
@@ -5,6 +5,7 @@
 #include "chromeos/dbus/biod/fake_biod_client.h"
 
 #include "base/bind.h"
+#include "base/memory/ptr_util.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "dbus/object_path.h"
@@ -12,27 +13,98 @@
 
 namespace chromeos {
 
+namespace {
+
+// Path of an enroll session. There should only be one enroll session at a
+// given time.
+const char kEnrollSessionObjectPath[] = "/EnrollSession/";
+
+// Header of the path of an record. A unique number will be appended when an
+// record is created.
+const char kRecordObjectPathPrefix[] = "/Record/";
+
+// Path of an auth session. There should only be one auth sesion at a given
+// time.
+const char kAuthSessionObjectPath[] = "/AuthSession/";
+
+}  // namespace
+
+// FakeRecord is the definition of a fake stored fingerprint template.
+struct FakeBiodClient::FakeRecord {
+  std::string user_id;
+  std::string label;
+  // A fake fingerprint is a vector which consists of all the strings which
+  // were "pressed" during the enroll session.
+  std::vector<std::string> fake_fingerprint;
+};
+
 FakeBiodClient::FakeBiodClient() {}
 
 FakeBiodClient::~FakeBiodClient() {}
 
-void FakeBiodClient::SendEnrollScanDone(biod::ScanResult type_result,
+void FakeBiodClient::SendEnrollScanDone(const std::string& fingerprint,
+                                        biod::ScanResult type_result,
                                         bool is_complete) {
+  // Enroll scan signals do nothing if an enroll session is not happening.
+  if (current_session_ != FingerprintSession::ENROLL)
+    return;
+
+  DCHECK(current_record_);
+  // The fake fingerprint gets appended to the current fake fingerprints.
+  current_record_->fake_fingerprint.push_back(fingerprint);
+
+  // If the enroll is complete, save the record and exit enroll mode.
+  if (is_complete) {
+    records_[current_record_path_] = std::move(current_record_);
+    current_record_path_ = dbus::ObjectPath();
+    current_record_.reset();
+    current_session_ = FingerprintSession::NONE;
+  }
+
   for (auto& observer : observers_)
     observer.BiodEnrollScanDoneReceived(type_result, is_complete);
 }
 
-void FakeBiodClient::SendAuthScanDone(biod::ScanResult type_result,
-                                      const AuthScanMatches& matches) {
+void FakeBiodClient::SendAuthScanDone(const std::string& fingerprint,
+                                      biod::ScanResult type_result) {
+  // Auth scan signals do nothing if an auth session is not happening.
+  if (current_session_ != FingerprintSession::AUTH)
+    return;
+
+  AuthScanMatches matches;
+  // Iterate through all the records to check if fingerprint is a match and
+  // populate |matches| accordingly. This searches through all the records and
+  // then each record's fake fingerprint, but neither of these should ever have
+  // more than five entries.
+  for (const auto& entry : records_) {
+    const std::unique_ptr<FakeRecord>& record = entry.second;
+    if (std::find(record->fake_fingerprint.begin(),
+                  record->fake_fingerprint.end(),
+                  fingerprint) != record->fake_fingerprint.end()) {
+      const std::string& user_id = record->user_id;
+      matches[user_id].push_back(record->label);
+    }
+  }
+
   for (auto& observer : observers_)
     observer.BiodAuthScanDoneReceived(type_result, matches);
 }
 
 void FakeBiodClient::SendSessionFailed() {
+  if (current_session_ == FingerprintSession::NONE)
+    return;
+
   for (auto& observer : observers_)
     observer.BiodSessionFailedReceived();
 }
 
+void FakeBiodClient::Reset() {
+  records_.clear();
+  current_record_.reset();
+  current_record_path_ = dbus::ObjectPath();
+  current_session_ = FingerprintSession::NONE;
+}
+
 void FakeBiodClient::Init(dbus::Bus* bus) {}
 
 void FakeBiodClient::AddObserver(Observer* observer) {
@@ -47,67 +119,109 @@
   return observers_.HasObserver(observer);
 }
 
-void FakeBiodClient::StartEnrollSession(const std::string& /* user_id */,
-                                        const std::string& /* label */,
+void FakeBiodClient::StartEnrollSession(const std::string& user_id,
+                                        const std::string& label,
                                         const ObjectPathCallback& callback) {
+  DCHECK_EQ(current_session_, FingerprintSession::NONE);
+
+  // Create the enrollment with |user_id|, |label| and a empty fake fingerprint.
+  current_record_path_ = dbus::ObjectPath(
+      kRecordObjectPathPrefix + std::to_string(next_record_unique_id_++));
+  current_record_ = base::MakeUnique<FakeRecord>();
+  current_record_->user_id = user_id;
+  current_record_->label = label;
+  current_session_ = FingerprintSession::ENROLL;
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, dbus::ObjectPath()));
+      FROM_HERE,
+      base::Bind(callback, dbus::ObjectPath(kEnrollSessionObjectPath)));
 }
 
-void FakeBiodClient::GetRecordsForUser(const std::string& /* user_id */,
+void FakeBiodClient::GetRecordsForUser(const std::string& user_id,
                                        const UserRecordsCallback& callback) {
+  std::vector<dbus::ObjectPath> records_object_paths;
+  for (const auto& record : records_) {
+    if (record.second->user_id == user_id)
+      records_object_paths.push_back(record.first);
+  }
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, std::vector<dbus::ObjectPath>()));
+      FROM_HERE, base::Bind(callback, records_object_paths));
 }
 
 void FakeBiodClient::DestroyAllRecords(const VoidDBusMethodCallback& callback) {
+  records_.clear();
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
 }
 
 void FakeBiodClient::StartAuthSession(const ObjectPathCallback& callback) {
+  DCHECK_EQ(current_session_, FingerprintSession::NONE);
+
+  current_session_ = FingerprintSession::AUTH;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, dbus::ObjectPath()));
+      FROM_HERE,
+      base::Bind(callback, dbus::ObjectPath(kAuthSessionObjectPath)));
 }
 
 void FakeBiodClient::RequestType(const BiometricTypeCallback& callback) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::Bind(callback, biod::BiometricType::BIOMETRIC_TYPE_UNKNOWN));
+      base::Bind(callback, biod::BiometricType::BIOMETRIC_TYPE_FINGERPRINT));
 }
 
 void FakeBiodClient::CancelEnrollSession(
-    const dbus::ObjectPath& /* enroll_session_path */,
+    const dbus::ObjectPath& enroll_session_path,
     const VoidDBusMethodCallback& callback) {
+  DCHECK_EQ(current_session_, FingerprintSession::ENROLL);
+  DCHECK_EQ(enroll_session_path.value(), kEnrollSessionObjectPath);
+
+  // Clean up the in progress enrollment.
+  current_record_.reset();
+  current_record_path_ = dbus::ObjectPath();
+  current_session_ = FingerprintSession::NONE;
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
 }
 
-void FakeBiodClient::EndAuthSession(
-    const dbus::ObjectPath& /* auth_session_path */,
-    const VoidDBusMethodCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
-}
-
-void FakeBiodClient::SetRecordLabel(const dbus::ObjectPath& /* record_path */,
-                                    const std::string& /* label */,
+void FakeBiodClient::EndAuthSession(const dbus::ObjectPath& auth_session_path,
                                     const VoidDBusMethodCallback& callback) {
+  DCHECK_EQ(current_session_, FingerprintSession::AUTH);
+  DCHECK_EQ(auth_session_path.value(), kAuthSessionObjectPath);
+
+  current_session_ = FingerprintSession::NONE;
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
 }
 
-void FakeBiodClient::RemoveRecord(const dbus::ObjectPath& /* record_path */,
+void FakeBiodClient::SetRecordLabel(const dbus::ObjectPath& record_path,
+                                    const std::string& label,
+                                    const VoidDBusMethodCallback& callback) {
+  if (records_.find(record_path) != records_.end())
+    records_[record_path]->label = label;
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
+}
+
+void FakeBiodClient::RemoveRecord(const dbus::ObjectPath& record_path,
                                   const VoidDBusMethodCallback& callback) {
+  records_.erase(record_path);
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(callback, DBUS_METHOD_CALL_SUCCESS));
 }
 
-void FakeBiodClient::RequestRecordLabel(
-    const dbus::ObjectPath& /* record_path */,
-    const LabelCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                base::Bind(callback, ""));
+void FakeBiodClient::RequestRecordLabel(const dbus::ObjectPath& record_path,
+                                        const LabelCallback& callback) {
+  std::string record_label;
+  if (records_.find(record_path) != records_.end())
+    record_label = records_[record_path]->label;
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(callback, record_label));
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/biod/fake_biod_client.h b/chromeos/dbus/biod/fake_biod_client.h
index 171826f..f5bb33b 100644
--- a/chromeos/dbus/biod/fake_biod_client.h
+++ b/chromeos/dbus/biod/fake_biod_client.h
@@ -5,19 +5,28 @@
 #ifndef CHROMEOS_DBUS_BIOD_FAKE_BIOD_CLIENT_H_
 #define CHROMEOS_DBUS_BIOD_FAKE_BIOD_CLIENT_H_
 
+#include <map>
+#include <string>
+
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "chromeos/chromeos_export.h"
 #include "chromeos/dbus/biod/biod_client.h"
+#include "dbus/object_path.h"
 
 namespace dbus {
 class Bus;
-class ObjectPath;
 }  // namespace dbus
 
 namespace chromeos {
 
-// A fake implementation of BiodClient.
+// A fake implementation of BiodClient. It emulates the real Biod daemon by
+// providing the same API and storing fingerprints locally. A fingerprint is
+// represented by a vector of strings. During enrollment, fake enrollments are
+// sent as strings. If they are successful they get added to the current
+// fingerprint, until a completed enroll scan is sent. An attempt scan is also
+// sent with a string. If that string matches any string in the stored
+// fingerprint vector, it is considered a match.
 class CHROMEOS_EXPORT FakeBiodClient : public BiodClient {
  public:
   FakeBiodClient();
@@ -25,11 +34,23 @@
 
   // Emulates the biod daemon by sending events which the daemon normally sends.
   // Notifies |observers_| about various events. These will be used in tests.
-  void SendEnrollScanDone(biod::ScanResult type_result, bool is_complete);
-  void SendAuthScanDone(biod::ScanResult type_result,
-                        const AuthScanMatches& matches);
+
+  // Emulates a scan that occurs during enrolling a new fingerprint.
+  // |fingerprint| is the fake data of the finger as a string. If |is_complete|
+  // is true the enroll session is finished, and the record is stored.
+  void SendEnrollScanDone(const std::string& fingerprint,
+                          biod::ScanResult type_result,
+                          bool is_complete);
+  // Emulates a scan that occurs during a authentication session. |fingerprint|
+  // is a string which represents the finger, and will be compared with all the
+  // stored fingerprints.
+  void SendAuthScanDone(const std::string& fingerprint,
+                        biod::ScanResult type_result);
   void SendSessionFailed();
 
+  // Clears all stored and current records from the fake storage.
+  void Reset();
+
   // BiodClient:
   void Init(dbus::Bus* bus) override;
   void AddObserver(Observer* observer) override;
@@ -56,6 +77,32 @@
                           const LabelCallback& callback) override;
 
  private:
+  struct FakeRecord;
+
+  // The current session of fingerprint storage. The session determines which
+  // events will be sent from user finger touches.
+  enum class FingerprintSession {
+    NONE,
+    ENROLL,
+    AUTH,
+  };
+
+  // The stored fingerprints.
+  std::map<dbus::ObjectPath, std::unique_ptr<FakeRecord>> records_;
+
+  // Current record in process of getting enrolled and its path. These are
+  // assigned at the start of an enroll session and freed when the enroll
+  // session is finished or cancelled.
+  dbus::ObjectPath current_record_path_;
+  std::unique_ptr<FakeRecord> current_record_;
+
+  // Unique indentifier that gets updated each time an enroll session is started
+  // to ensure each record is stored at a different path.
+  int next_record_unique_id_ = 1;
+
+  // The current session of the fake storage.
+  FingerprintSession current_session_ = FingerprintSession::NONE;
+
   base::ObserverList<Observer> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeBiodClient);
diff --git a/chromeos/dbus/biod/fake_biod_client_unittest.cc b/chromeos/dbus/biod/fake_biod_client_unittest.cc
new file mode 100644
index 0000000..521c3e6
--- /dev/null
+++ b/chromeos/dbus/biod/fake_biod_client_unittest.cc
@@ -0,0 +1,308 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/dbus/biod/fake_biod_client.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/strings/string_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chromeos/dbus/biod/test_utils.h"
+#include "dbus/object_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace biod;
+
+namespace chromeos {
+
+namespace {
+
+const char kTestUserId[] = "testuser@gmail.com";
+const char kTestLabel[] = "testLabel";
+// Template of a scan string to be used in GenerateTestFingerprint. The # and $
+// are two wildcards that will be replaced by numbers to ensure unique scans.
+const char kTestScan[] = "finger#scan$";
+
+}  // namepsace
+
+class FakeBiodClientTest : public testing::Test {
+ public:
+  FakeBiodClientTest()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_) {}
+  ~FakeBiodClientTest() override {}
+
+  // Returns the stored records for |user_id|. Verified to work in
+  // TestGetRecordsForUser.
+  std::vector<dbus::ObjectPath> GetRecordsForUser(const std::string& user_id) {
+    std::vector<dbus::ObjectPath> enrollment_paths;
+    fake_biod_client_.GetRecordsForUser(
+        user_id,
+        base::Bind(&test_utils::CopyObjectPathArray, &enrollment_paths));
+    task_runner_->RunUntilIdle();
+    return enrollment_paths;
+  }
+
+  // Helper function which enrolls a fingerprint. Each element in
+  // |fingerprint_data| corresponds to a finger tap.
+  void EnrollFingerprint(const std::string& id,
+                         const std::string& label,
+                         const std::vector<std::string>& fingerprint_data) {
+    ASSERT_FALSE(fingerprint_data.empty());
+
+    dbus::ObjectPath returned_path;
+    fake_biod_client_.StartEnrollSession(
+        id, label, base::Bind(&test_utils::CopyObjectPath, &returned_path));
+    task_runner_->RunUntilIdle();
+    EXPECT_NE(dbus::ObjectPath(), returned_path);
+
+    // Send |fingerprint_data| size - 1 incomplete scans, then finish the
+    // enrollment by sending a complete scan signal.
+    for (size_t i = 0; i < fingerprint_data.size(); ++i) {
+      fake_biod_client_.SendEnrollScanDone(
+          fingerprint_data[i], SCAN_RESULT_SUCCESS,
+          i == fingerprint_data.size() - 1 /* is_complete */);
+    }
+  }
+
+  // Helper function which enrolls |n| fingerprints with the same |id|, |label|
+  // and |fingerprint_data|.
+  void EnrollNTestFingerprints(const std::string& id,
+                               const std::string& label,
+                               const std::vector<std::string>& fingerprint_data,
+                               int n) {
+    for (int i = 0; i < n; ++i)
+      EnrollFingerprint(id, label, fingerprint_data);
+  }
+
+  // Creates a new unique fingerprint consisting of unique scans.
+  std::vector<std::string> GenerateTestFingerprint(int scans) {
+    EXPECT_GE(scans, 0);
+    num_test_fingerprints_++;
+
+    std::vector<std::string> fingerprint;
+    for (int i = 0; i < scans; ++i) {
+      std::string scan = kTestScan;
+      base::ReplaceSubstringsAfterOffset(
+          &scan, 0, "#", std::to_string(num_test_fingerprints_));
+      base::ReplaceSubstringsAfterOffset(&scan, 0, "$", std::to_string(i));
+      fingerprint.push_back(scan);
+    }
+    return fingerprint;
+  }
+
+ protected:
+  FakeBiodClient fake_biod_client_;
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+
+  // This number is incremented each time GenerateTestFingerprint is called to
+  // ensure each fingerprint is unique.
+  int num_test_fingerprints_ = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeBiodClientTest);
+};
+
+TEST_F(FakeBiodClientTest, TestEnrollSessionWorkflow) {
+  test_utils::TestBiodObserver observer;
+  fake_biod_client_.AddObserver(&observer);
+
+  const std::vector<std::string>& kTestFingerprint = GenerateTestFingerprint(2);
+  // Verify that successful enrollments get stored as expected. A fingerprint
+  // that was created with 2 scans should have 1 incomplete scan and 1 complete
+  // scan.
+  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint);
+  EXPECT_EQ(1u, GetRecordsForUser(kTestUserId).size());
+  EXPECT_EQ(1, observer.num_incomplete_enroll_scans_received());
+  EXPECT_EQ(1, observer.num_complete_enroll_scans_received());
+
+  // Verify that the enroll session worflow can be used repeatedly by enrolling
+  // 3 more fingerprints (each with 1 incomplete and 1 complete scan).
+  observer.ResetAllCounts();
+  EnrollNTestFingerprints(kTestUserId, kTestLabel, kTestFingerprint, 3);
+  EXPECT_EQ(4u, GetRecordsForUser(kTestUserId).size());
+  EXPECT_EQ(3, observer.num_incomplete_enroll_scans_received());
+  EXPECT_EQ(3, observer.num_complete_enroll_scans_received());
+}
+
+// Test authentication when one user has one or more fingerprints registered.
+// This should be the normal scenario.
+TEST_F(FakeBiodClientTest, TestAuthSessionWorkflowSingleUser) {
+  test_utils::TestBiodObserver observer;
+  fake_biod_client_.AddObserver(&observer);
+  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());
+
+  const std::vector<std::string>& kTestFingerprint1 =
+      GenerateTestFingerprint(2);
+  const std::vector<std::string>& kTestFingerprint2 =
+      GenerateTestFingerprint(2);
+  const std::vector<std::string>& kTestFingerprint3 =
+      GenerateTestFingerprint(2);
+  const std::vector<std::string>& kTestFingerprint4 =
+      GenerateTestFingerprint(2);
+
+  // Add two fingerprints |kTestFingerprint1| and |kTestFingerprint2| and start
+  // an auth session.
+  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint1);
+  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint2);
+  dbus::ObjectPath returned_path;
+  fake_biod_client_.StartAuthSession(
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
+  task_runner_->RunUntilIdle();
+  EXPECT_NE(returned_path, dbus::ObjectPath());
+
+  // Verify that by sending two attempt signals of fingerprints that have been
+  // enrolled, the observer should receive two matches and zero non-matches.
+  fake_biod_client_.SendAuthScanDone(kTestFingerprint1[0], SCAN_RESULT_SUCCESS);
+  fake_biod_client_.SendAuthScanDone(kTestFingerprint2[0], SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(2, observer.num_matched_auth_scans_received());
+  EXPECT_EQ(0, observer.num_unmatched_auth_scans_received());
+
+  // Verify that by sending two attempt signals of fingerprints that have not
+  // been enrolled, the observer should receive two non-matches and zero
+  // matches.
+  observer.ResetAllCounts();
+  fake_biod_client_.SendAuthScanDone(kTestFingerprint3[0], SCAN_RESULT_SUCCESS);
+  fake_biod_client_.SendAuthScanDone(kTestFingerprint4[0], SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(0, observer.num_matched_auth_scans_received());
+  EXPECT_EQ(2, observer.num_unmatched_auth_scans_received());
+}
+
+// Test authentication when multiple users have fingerprints registered. Cover
+// cases such as when both users use the same labels, a user had registered the
+// same fingerprint multiple times, or two users use the same fingerprint.
+TEST_F(FakeBiodClientTest, TestAuthenticateWorkflowMultipleUsers) {
+  test_utils::TestBiodObserver observer;
+  fake_biod_client_.AddObserver(&observer);
+  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());
+
+  // Add two users, who have scanned three fingers between the two of them.
+  const std::string kUserOne = std::string(kTestUserId) + "1";
+  const std::string kUserTwo = std::string(kTestUserId) + "2";
+
+  const std::string kLabelOne = std::string(kTestLabel) + "1";
+  const std::string kLabelTwo = std::string(kTestLabel) + "2";
+  const std::string kLabelThree = std::string(kTestLabel) + "3";
+
+  // Generate 2 test fingerprints per user.
+  const std::vector<std::string>& kUser1Finger1 = GenerateTestFingerprint(2);
+  const std::vector<std::string>& kUser1Finger2 = GenerateTestFingerprint(2);
+  const std::vector<std::string>& kUser2Finger1 = GenerateTestFingerprint(2);
+  const std::vector<std::string>& kUser2Finger2 = GenerateTestFingerprint(2);
+
+  EnrollFingerprint(kUserOne, kLabelOne, kUser1Finger1);
+  EnrollFingerprint(kUserOne, kLabelTwo, kUser1Finger2);
+  // User one has registered finger two twice.
+  EnrollFingerprint(kUserOne, kLabelThree, kUser1Finger2);
+  EnrollFingerprint(kUserTwo, kLabelOne, kUser2Finger1);
+  EnrollFingerprint(kUserTwo, kLabelTwo, kUser2Finger2);
+  // User two has allowed user one to unlock their account with their first
+  // finger.
+  EnrollFingerprint(kUserTwo, kLabelThree, kUser1Finger1);
+
+  dbus::ObjectPath returned_path;
+  fake_biod_client_.StartAuthSession(
+      base::Bind(&test_utils::CopyObjectPath, &returned_path));
+  task_runner_->RunUntilIdle();
+  EXPECT_NE(returned_path, dbus::ObjectPath());
+
+  // Verify that if a user registers the same finger to two different labels,
+  // both labels are returned as matches.
+  AuthScanMatches expected_auth_scans_matches;
+  expected_auth_scans_matches[kUserOne] = {kLabelTwo, kLabelThree};
+  fake_biod_client_.SendAuthScanDone(kUser1Finger2[0], SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());
+
+  // Verify that a fingerprint associated with one user and one label returns a
+  // match with one user and one label.
+  expected_auth_scans_matches.clear();
+  expected_auth_scans_matches[kUserTwo] = {kLabelOne};
+  fake_biod_client_.SendAuthScanDone(kUser2Finger1[0], SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());
+
+  // Verify if two users register the same fingerprint, the matches contain
+  // both users.
+  expected_auth_scans_matches.clear();
+  expected_auth_scans_matches[kUserOne] = {kLabelOne};
+  expected_auth_scans_matches[kUserTwo] = {kLabelThree};
+  fake_biod_client_.SendAuthScanDone(kUser1Finger1[0], SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());
+
+  // Verify if a unregistered finger is scanned, the matches are empty.
+  expected_auth_scans_matches.clear();
+  fake_biod_client_.SendAuthScanDone("Unregistered", SCAN_RESULT_SUCCESS);
+  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());
+}
+
+TEST_F(FakeBiodClientTest, TestGetRecordsForUser) {
+  // Verify that initially |kTestUserId| will have no fingerprints.
+  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());
+
+  // Verify that after enrolling 2 fingerprints, a GetRecords call return 2
+  // items.
+  EnrollNTestFingerprints(kTestUserId, kTestLabel, GenerateTestFingerprint(2),
+                          2);
+  EXPECT_EQ(2u, GetRecordsForUser(kTestUserId).size());
+
+  // Verify that GetRecords call for a user with no registered fingerprints
+  // should return 0 items.
+  EXPECT_EQ(0u, GetRecordsForUser("noRegisteredFingerprints@gmail.com").size());
+}
+
+TEST_F(FakeBiodClientTest, TestDestroyingRecords) {
+  // Verify that after enrolling 2 fingerprints and destroying them, 0
+  // fingerprints will remain.
+  EnrollNTestFingerprints(kTestUserId, kTestLabel, GenerateTestFingerprint(2),
+                          2);
+  EXPECT_EQ(2u, GetRecordsForUser(kTestUserId).size());
+  DBusMethodCallStatus returned_status;
+  fake_biod_client_.DestroyAllRecords(
+      base::Bind(&test_utils::CopyDBusMethodCallStatus, &returned_status));
+  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());
+}
+
+TEST_F(FakeBiodClientTest, TestGetAndSetRecordLabels) {
+  const std::string kLabelOne = "Finger 1";
+  const std::string kLabelTwo = "Finger 2";
+
+  EnrollFingerprint(kTestUserId, kLabelOne, GenerateTestFingerprint(2));
+  EnrollFingerprint(kTestUserId, kLabelTwo, GenerateTestFingerprint(2));
+  EXPECT_EQ(2u, GetRecordsForUser(kTestUserId).size());
+  std::vector<dbus::ObjectPath> enrollment_paths;
+  fake_biod_client_.GetRecordsForUser(
+      kTestUserId,
+      base::Bind(&test_utils::CopyObjectPathArray, &enrollment_paths));
+  task_runner_->RunUntilIdle();
+  EXPECT_EQ(2u, enrollment_paths.size());
+
+  // Verify the labels we get using GetLabel are the same as the one we
+  // originally set.
+  std::string returned_str;
+  fake_biod_client_.RequestRecordLabel(
+      enrollment_paths[0], base::Bind(&test_utils::CopyString, &returned_str));
+  task_runner_->RunUntilIdle();
+  EXPECT_EQ(kLabelOne, returned_str);
+
+  returned_str = "";
+  fake_biod_client_.RequestRecordLabel(
+      enrollment_paths[1], base::Bind(&test_utils::CopyString, &returned_str));
+  task_runner_->RunUntilIdle();
+  EXPECT_EQ(kLabelTwo, returned_str);
+
+  // Verify that by setting a new label, getting the label will return the value
+  // of the new label.
+  const std::string kNewLabelTwo = "Finger 2 New";
+  DBusMethodCallStatus returned_status;
+  fake_biod_client_.SetRecordLabel(
+      enrollment_paths[1], kNewLabelTwo,
+      base::Bind(&test_utils::CopyDBusMethodCallStatus, &returned_status));
+  fake_biod_client_.RequestRecordLabel(
+      enrollment_paths[1], base::Bind(&test_utils::CopyString, &returned_str));
+  task_runner_->RunUntilIdle();
+  EXPECT_EQ(kNewLabelTwo, returned_str);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/biod/test_utils.cc b/chromeos/dbus/biod/test_utils.cc
new file mode 100644
index 0000000..46ebde1e
--- /dev/null
+++ b/chromeos/dbus/biod/test_utils.cc
@@ -0,0 +1,85 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/dbus/biod/test_utils.h"
+
+#include "base/logging.h"
+#include "dbus/object_path.h"
+
+namespace chromeos {
+namespace test_utils {
+
+void CopyObjectPath(dbus::ObjectPath* dest_path,
+                    const dbus::ObjectPath& src_path) {
+  CHECK(dest_path);
+  *dest_path = src_path;
+}
+
+void CopyObjectPathArray(
+    std::vector<dbus::ObjectPath>* dest_object_paths,
+    const std::vector<dbus::ObjectPath>& src_object_paths) {
+  CHECK(dest_object_paths);
+  *dest_object_paths = src_object_paths;
+}
+
+void CopyString(std::string* dest_str, const std::string& src_str) {
+  CHECK(dest_str);
+  *dest_str = src_str;
+}
+
+void CopyDBusMethodCallStatus(DBusMethodCallStatus* dest_status,
+                              DBusMethodCallStatus src_status) {
+  CHECK(dest_status);
+  *dest_status = src_status;
+}
+
+void CopyBiometricType(biod::BiometricType* dest_type,
+                       biod::BiometricType src_type) {
+  CHECK(dest_type);
+  *dest_type = src_type;
+}
+
+TestBiodObserver::TestBiodObserver() {}
+
+TestBiodObserver::~TestBiodObserver() {}
+
+int TestBiodObserver::NumEnrollScansReceived() const {
+  return num_complete_enroll_scans_received_ +
+         num_incomplete_enroll_scans_received_;
+}
+
+int TestBiodObserver::NumAuthScansReceived() const {
+  return num_matched_auth_scans_received_ + num_unmatched_auth_scans_received_;
+}
+
+void TestBiodObserver::ResetAllCounts() {
+  num_complete_enroll_scans_received_ = 0;
+  num_incomplete_enroll_scans_received_ = 0;
+  num_matched_auth_scans_received_ = 0;
+  num_unmatched_auth_scans_received_ = 0;
+  num_failures_received_ = 0;
+}
+
+void TestBiodObserver::BiodServiceRestarted() {}
+
+void TestBiodObserver::BiodEnrollScanDoneReceived(biod::ScanResult scan_result,
+                                                  bool is_complete) {
+  is_complete ? num_complete_enroll_scans_received_++
+              : num_incomplete_enroll_scans_received_++;
+}
+
+void TestBiodObserver::BiodAuthScanDoneReceived(
+    biod::ScanResult scan_result,
+    const AuthScanMatches& matches) {
+  matches.empty() ? num_unmatched_auth_scans_received_++
+                  : num_matched_auth_scans_received_++;
+  last_auth_scan_matches_ = matches;
+}
+
+void TestBiodObserver::BiodSessionFailedReceived() {
+  num_failures_received_++;
+}
+
+}  // namepsace test_utils
+}  // namespace chromeos
diff --git a/chromeos/dbus/biod/test_utils.h b/chromeos/dbus/biod/test_utils.h
new file mode 100644
index 0000000..68e64ce4
--- /dev/null
+++ b/chromeos/dbus/biod/test_utils.h
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_BIOD_TEST_UTILS_H_
+#define CHROMEOS_DBUS_BIOD_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "chromeos/dbus/biod/biod_client.h"
+
+namespace dbus {
+class ObjectPath;
+}
+
+namespace chromeos {
+namespace test_utils {
+
+// Copies |src_path| to |dest_path|.
+void CopyObjectPath(dbus::ObjectPath* dest_path,
+                    const dbus::ObjectPath& src_path);
+
+// Copies |src_object_paths| to |dst_object_paths|.
+void CopyObjectPathArray(std::vector<dbus::ObjectPath>* dest_object_paths,
+                         const std::vector<dbus::ObjectPath>& src_object_paths);
+
+// Copies |src_str| to |dest_str|.
+void CopyString(std::string* dest_str, const std::string& src_str);
+
+// Copies |src_status| to |dest_status|.
+void CopyDBusMethodCallStatus(DBusMethodCallStatus* dest_status,
+                              DBusMethodCallStatus src_status);
+
+// Copies |src_type| to |dest_type|.
+void CopyBiometricType(biod::BiometricType* dest_type,
+                       biod::BiometricType src_type);
+
+// Implementation of BiodClient::Observer for testing.
+class TestBiodObserver : public BiodClient::Observer {
+ public:
+  TestBiodObserver();
+  ~TestBiodObserver() override;
+
+  int num_complete_enroll_scans_received() const {
+    return num_complete_enroll_scans_received_;
+  }
+  int num_incomplete_enroll_scans_received() const {
+    return num_incomplete_enroll_scans_received_;
+  }
+  int num_matched_auth_scans_received() const {
+    return num_matched_auth_scans_received_;
+  }
+  int num_unmatched_auth_scans_received() const {
+    return num_unmatched_auth_scans_received_;
+  }
+  int num_failures_received() const { return num_failures_received_; }
+  const AuthScanMatches& last_auth_scan_matches() const {
+    return last_auth_scan_matches_;
+  }
+
+  int NumEnrollScansReceived() const;
+  int NumAuthScansReceived() const;
+
+  void ResetAllCounts();
+
+  // BiodClient::Observer:
+  void BiodServiceRestarted() override;
+  void BiodEnrollScanDoneReceived(biod::ScanResult scan_result,
+                                  bool is_complete) override;
+  void BiodAuthScanDoneReceived(biod::ScanResult scan_result,
+                                const AuthScanMatches& matches) override;
+  void BiodSessionFailedReceived() override;
+
+ private:
+  int num_complete_enroll_scans_received_ = 0;
+  int num_incomplete_enroll_scans_received_ = 0;
+  int num_matched_auth_scans_received_ = 0;
+  int num_unmatched_auth_scans_received_ = 0;
+  int num_failures_received_ = 0;
+
+  // When auth scan is received, store the result.
+  AuthScanMatches last_auth_scan_matches_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestBiodObserver);
+};
+
+}  // namespace test_utils
+}  // chromeos
+
+#endif  // CHROMEOS_DBUS_BIOD_TEST_UTILS_H_
diff --git a/components/test/data/web_database/OWNERS b/components/test/data/web_database/OWNERS
index f9f5300d..b9fe305 100644
--- a/components/test/data/web_database/OWNERS
+++ b/components/test/data/web_database/OWNERS
@@ -1,6 +1,6 @@
 pkasting@chromium.org
 
 # For sqlite stuff:
-shess@chromium.org
+pwnall@chromium.org
 
 # COMPONENT: Test
diff --git a/components/webdata/OWNERS b/components/webdata/OWNERS
index 882e1e2..986f4a93 100644
--- a/components/webdata/OWNERS
+++ b/components/webdata/OWNERS
@@ -1,6 +1,6 @@
 pkasting@chromium.org
 
 # For sqlite stuff:
-shess@chromium.org
+pwnall@chromium.org
 
 # COMPONENT: Internals
diff --git a/components/webdata_services/OWNERS b/components/webdata_services/OWNERS
index 882e1e2..986f4a93 100644
--- a/components/webdata_services/OWNERS
+++ b/components/webdata_services/OWNERS
@@ -1,6 +1,6 @@
 pkasting@chromium.org
 
 # For sqlite stuff:
-shess@chromium.org
+pwnall@chromium.org
 
 # COMPONENT: Internals
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 5c55a852..a405429f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -70,7 +70,6 @@
     "//crypto",
     "//device/bluetooth",
     "//device/gamepad",
-    "//device/generic_sensor",
     "//device/geolocation",
     "//device/geolocation/public/interfaces",
     "//device/nfc:mojo_bindings",
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 260b7da..55d6a08e 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -95,7 +95,6 @@
 #include "content/public/common/service_names.mojom.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/common/url_utils.h"
-#include "device/generic_sensor/sensor_provider_impl.h"
 #include "device/geolocation/geolocation_service_context.h"
 #include "device/vr/features.h"
 #include "device/wake_lock/public/interfaces/wake_lock_context.mojom.h"
@@ -2513,13 +2512,6 @@
       base::Bind(&IgnoreInterfaceRequest<device::mojom::VRService>));
 #endif
 
-  if (base::FeatureList::IsEnabled(features::kGenericSensor)) {
-    GetInterfaceRegistry()->AddInterface(
-        base::Bind(&device::SensorProviderImpl::Create,
-                   BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE)),
-        BrowserThread::GetTaskRunnerForThread(BrowserThread::IO));
-  }
-
 #if BUILDFLAG(ENABLE_WEBRTC)
   // BrowserMainLoop::GetInstance() may be null on unit tests.
   if (BrowserMainLoop::GetInstance()) {
diff --git a/content/child/BUILD.gn b/content/child/BUILD.gn
index a63855b1..f1ff86d 100644
--- a/content/child/BUILD.gn
+++ b/content/child/BUILD.gn
@@ -238,6 +238,7 @@
     "//mojo/common",
     "//mojo/edk/system",
     "//net",
+    "//services/device/public/cpp:device_features",
     "//services/device/public/cpp/power_monitor",
     "//services/device/public/interfaces:constants",
     "//services/resource_coordinator/public/cpp",
diff --git a/content/child/DEPS b/content/child/DEPS
index 3a07c1cb..da61fe55 100644
--- a/content/child/DEPS
+++ b/content/child/DEPS
@@ -9,6 +9,7 @@
 
   "+content/app/strings/grit",  # For generated headers
   "+content/public/child",
+  "+services/device/public/cpp/device_features.h",
   "+services/device/public/cpp/power_monitor",
   "+services/device/public/interfaces",
   "+services/resource_coordinator",
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index d8c840e..b334d5b 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -15,6 +15,7 @@
 #include "content/common/content_switches_internal.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "services/device/public/cpp/device_features.h"
 #include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
 #include "ui/gl/gl_switches.h"
 #include "ui/native_theme/native_theme_switches.h"
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json
index bf6b7ba..6e3177d 100644
--- a/content/public/app/mojo/content_browser_manifest.json
+++ b/content/public/app/mojo/content_browser_manifest.json
@@ -91,7 +91,6 @@
           "content::mojom::BrowserTarget",
           "device::mojom::VRService",
           "device::mojom::GeolocationService",
-          "device::mojom::SensorProvider",
           "device::mojom::WakeLockService",
           "device::nfc::mojom::NFC",
           "device::usb::DeviceManager",
diff --git a/content/public/app/mojo/content_renderer_manifest.json b/content/public/app/mojo/content_renderer_manifest.json
index 5b226bc..40581d3 100644
--- a/content/public/app/mojo/content_renderer_manifest.json
+++ b/content/public/app/mojo/content_renderer_manifest.json
@@ -22,6 +22,7 @@
         "content_browser": [ "renderer" ],
         "device": [
           "device:battery_monitor",
+          "device:generic_sensor",
           "device:power_monitor",
           "device:screen_orientation",
           "device:sensors",
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 4a1d40e..68111f4 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -91,11 +91,6 @@
 const base::Feature kGamepadExtensions{"GamepadExtensions",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables sensors based on Generic Sensor API:
-// https://w3c.github.io/sensors/
-const base::Feature kGenericSensor{"GenericSensor",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Causes the implementations of guests (inner WebContents) to use
 // out-of-process iframes.
 const base::Feature kGuestViewCrossProcessFrames{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 337220a7..80e7807 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -33,7 +33,6 @@
 CONTENT_EXPORT extern const base::Feature
     kFramebustingNeedsSameOriginOrUserGesture;
 CONTENT_EXPORT extern const base::Feature kGamepadExtensions;
-CONTENT_EXPORT extern const base::Feature kGenericSensor;
 CONTENT_EXPORT extern const base::Feature kGuestViewCrossProcessFrames;
 CONTENT_EXPORT extern const base::Feature kHeapCompaction;
 CONTENT_EXPORT extern const base::Feature kLazyParseCSS;
diff --git a/content/renderer/media/webmediaplayer_ms.cc b/content/renderer/media/webmediaplayer_ms.cc
index 98f53696..e2adb547 100644
--- a/content/renderer/media/webmediaplayer_ms.cc
+++ b/content/renderer/media/webmediaplayer_ms.cc
@@ -578,6 +578,9 @@
 bool WebMediaPlayerMS::CopyVideoTextureToPlatformTexture(
     gpu::gles2::GLES2Interface* gl,
     unsigned int texture,
+    unsigned internal_format,
+    unsigned format,
+    unsigned type,
     bool premultiply_alpha,
     bool flip_y) {
   TRACE_EVENT0("media", "WebMediaPlayerMS:copyVideoTextureToPlatformTexture");
@@ -598,7 +601,8 @@
   context_3d = media::Context3D(provider->ContextGL(), provider->GrContext());
   DCHECK(context_3d.gl);
   return video_renderer_.CopyVideoFrameTexturesToGLTexture(
-      context_3d, gl, video_frame.get(), texture, premultiply_alpha, flip_y);
+      context_3d, gl, video_frame.get(), texture, internal_format, format, type,
+      premultiply_alpha, flip_y);
 }
 
 bool WebMediaPlayerMS::TexImageImpl(TexImageFunctionID functionID,
diff --git a/content/renderer/media/webmediaplayer_ms.h b/content/renderer/media/webmediaplayer_ms.h
index a3600eb..d44d94e 100644
--- a/content/renderer/media/webmediaplayer_ms.h
+++ b/content/renderer/media/webmediaplayer_ms.h
@@ -155,6 +155,9 @@
 
   bool CopyVideoTextureToPlatformTexture(gpu::gles2::GLES2Interface* gl,
                                          unsigned int texture,
+                                         unsigned internal_format,
+                                         unsigned format,
+                                         unsigned type,
                                          bool premultiply_alpha,
                                          bool flip_y) override;
 
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 507f23b..c32c1cb1 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -44,10 +44,6 @@
     self.Flaky('conformance2/query/occlusion-query.html', bug=603168)
     self.Fail('conformance2/glsl3/tricky-loop-conditions.html', bug=483282)
 
-    # Temporary suppression; will be removed after bug fix.
-    self.Fail('conformance/textures/misc/texture-corner-case-videos.html',
-              bug=701060)
-
     # canvas.commit() promise synchronization isn't fully reliable yet.
     self.Fail('conformance/offscreencanvas/offscreencanvas-resize.html',
               bug=709484)
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index cdd41f4..a314343 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -101,10 +101,6 @@
     self.Fail('conformance/textures/misc/tex-sub-image-2d-bad-args.html',
         bug=625738)
 
-    # Temporary suppression; will be removed after bug fix.
-    self.Fail('conformance/textures/misc/texture-corner-case-videos.html',
-              bug=701060)
-
     # canvas.commit() promise synchronization isn't fully reliable yet.
     self.Fail('conformance/offscreencanvas/offscreencanvas-resize.html',
               bug=709484)
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 366ada9..7c10f9f 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -943,6 +943,9 @@
 bool WebMediaPlayerImpl::CopyVideoTextureToPlatformTexture(
     gpu::gles2::GLES2Interface* gl,
     unsigned int texture,
+    unsigned internal_format,
+    unsigned format,
+    unsigned type,
     bool premultiply_alpha,
     bool flip_y) {
   DCHECK(main_task_runner_->BelongsToCurrentThread());
@@ -963,7 +966,8 @@
   if (!context_3d_cb_.is_null())
     context_3d = context_3d_cb_.Run();
   return skcanvas_video_renderer_.CopyVideoFrameTexturesToGLTexture(
-      context_3d, gl, video_frame.get(), texture, premultiply_alpha, flip_y);
+      context_3d, gl, video_frame.get(), texture, internal_format, format, type,
+      premultiply_alpha, flip_y);
 }
 
 void WebMediaPlayerImpl::SetContentDecryptionModule(
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
index 29863b0..8e1a16b5 100644
--- a/media/blink/webmediaplayer_impl.h
+++ b/media/blink/webmediaplayer_impl.h
@@ -173,6 +173,9 @@
 
   bool CopyVideoTextureToPlatformTexture(gpu::gles2::GLES2Interface* gl,
                                          unsigned int texture,
+                                         unsigned internal_format,
+                                         unsigned format,
+                                         unsigned type,
                                          bool premultiply_alpha,
                                          bool flip_y) override;
 
diff --git a/media/renderers/skcanvas_video_renderer.cc b/media/renderers/skcanvas_video_renderer.cc
index 1e2e9b2..d60e854 100644
--- a/media/renderers/skcanvas_video_renderer.cc
+++ b/media/renderers/skcanvas_video_renderer.cc
@@ -160,6 +160,20 @@
   return img;
 }
 
+bool VideoTextureNeedsClipping(const VideoFrame* video_frame) {
+  // There are multiple reasons that the size of the video frame's
+  // visible rectangle may differ from the coded size, including the
+  // encoder rounding up to the size of a macroblock, or use of
+  // non-square pixels.
+  //
+  // Some callers of these APIs (HTMLVideoElement and the 2D canvas
+  // context) already clip to the video frame's visible rectangle.
+  // WebGL on the other hand assumes that only the valid pixels are
+  // contained in the destination texture. This helper function
+  // determines whether this slower path is needed.
+  return video_frame->visible_rect().size() != video_frame->coded_size();
+}
+
 // Creates a SkImage from a |video_frame| backed by native resources.
 // The SkImage will take ownership of the underlying resource.
 sk_sp<SkImage> NewSkImageFromVideoFrameNative(VideoFrame* video_frame,
@@ -184,12 +198,10 @@
     gl->GenTextures(1, &source_texture);
     DCHECK(source_texture);
     gl->BindTexture(GL_TEXTURE_2D, source_texture);
-    const gfx::Size& natural_size = video_frame->natural_size();
-    gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, natural_size.width(),
-                   natural_size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                   nullptr);
     SkCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
-        gl, video_frame, source_texture, true, false);
+        gl, video_frame,
+        SkCanvasVideoRenderer::SingleFrameForVideoElementOrCanvas,
+        source_texture, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, true, false);
   } else {
     gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
     source_texture = gl->CreateAndConsumeTextureCHROMIUM(
@@ -753,7 +765,11 @@
 void SkCanvasVideoRenderer::CopyVideoFrameSingleTextureToGLTexture(
     gpu::gles2::GLES2Interface* gl,
     VideoFrame* video_frame,
+    SingleFrameCopyMode copy_mode,
     unsigned int texture,
+    unsigned int internal_format,
+    unsigned int format,
+    unsigned int type,
     bool premultiply_alpha,
     bool flip_y) {
   DCHECK(video_frame);
@@ -776,14 +792,35 @@
   // "flip_y == true" means to reverse the video orientation while
   // "flip_y == false" means to keep the intrinsic orientation.
 
-  // The video's texture might be larger than the natural size because
-  // the encoder might have had to round up to the size of a macroblock.
-  // Make sure to only copy the natural size to avoid putting garbage
-  // into the bottom of the destination texture.
-  const gfx::Size& natural_size = video_frame->natural_size();
-  gl->CopySubTextureCHROMIUM(source_texture, 0, GL_TEXTURE_2D, texture, 0, 0, 0,
-                             0, 0, natural_size.width(), natural_size.height(),
-                             flip_y, premultiply_alpha, false);
+  if (copy_mode == SingleFrameForVideoElementOrCanvas ||
+      !VideoTextureNeedsClipping(video_frame)) {
+    // No need to clip the source video texture.
+    gl->CopyTextureCHROMIUM(source_texture, 0, GL_TEXTURE_2D, texture, 0,
+                            internal_format, type, flip_y, premultiply_alpha,
+                            false);
+  } else {
+    // Must reallocate the destination texture and copy only a sub-portion.
+    gfx::Rect dest_rect = video_frame->visible_rect();
+#if DCHECK_IS_ON()
+    // The caller should have bound _texture_ to the GL_TEXTURE_2D
+    // binding point already.
+    GLuint current_texture = 0;
+    gl->GetIntegerv(GL_TEXTURE_BINDING_2D,
+                    reinterpret_cast<GLint*>(&current_texture));
+    DCHECK_EQ(current_texture, texture);
+    // There should always be enough data in the source texture to
+    // cover this copy.
+    DCHECK_LE(dest_rect.width(), video_frame->coded_size().width());
+    DCHECK_LE(dest_rect.height(), video_frame->coded_size().height());
+#endif
+    gl->TexImage2D(GL_TEXTURE_2D, 0, internal_format, dest_rect.width(),
+                   dest_rect.height(), 0, format, type, nullptr);
+    gl->CopySubTextureCHROMIUM(source_texture, 0, GL_TEXTURE_2D, texture, 0, 0,
+                               0, dest_rect.x(), dest_rect.y(),
+                               dest_rect.width(), dest_rect.height(), flip_y,
+                               premultiply_alpha, false);
+  }
+
   gl->DeleteTextures(1, &source_texture);
   gl->Flush();
 
@@ -796,6 +833,9 @@
     gpu::gles2::GLES2Interface* destination_gl,
     const scoped_refptr<VideoFrame>& video_frame,
     unsigned int texture,
+    unsigned int internal_format,
+    unsigned int format,
+    unsigned int type,
     bool premultiply_alpha,
     bool flip_y) {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -832,15 +872,35 @@
         destination_gl->CreateAndConsumeTextureCHROMIUM(
             mailbox_holder.texture_target, mailbox_holder.mailbox.name);
 
-    // The video's texture might be larger than the natural size because
-    // the encoder might have had to round up to the size of a macroblock.
-    // Make sure to only copy the natural size to avoid putting garbage
-    // into the bottom of the destination texture.
-    const gfx::Size& natural_size = video_frame->natural_size();
-    destination_gl->CopySubTextureCHROMIUM(
-        intermediate_texture, 0, GL_TEXTURE_2D, texture, 0, 0, 0, 0, 0,
-        natural_size.width(), natural_size.height(), flip_y, premultiply_alpha,
-        false);
+    // See whether the source video texture must be clipped.
+    if (VideoTextureNeedsClipping(video_frame.get())) {
+      // Reallocate destination texture and copy only valid region.
+      gfx::Rect dest_rect = video_frame->visible_rect();
+#if DCHECK_IS_ON()
+      // The caller should have bound _texture_ to the GL_TEXTURE_2D
+      // binding point already.
+      GLuint current_texture = 0;
+      destination_gl->GetIntegerv(GL_TEXTURE_BINDING_2D,
+                                  reinterpret_cast<GLint*>(&current_texture));
+      DCHECK_EQ(current_texture, texture);
+      // There should always be enough data in the source texture to
+      // cover this copy.
+      DCHECK_LE(dest_rect.width(), video_frame->coded_size().width());
+      DCHECK_LE(dest_rect.height(), video_frame->coded_size().height());
+#endif
+      destination_gl->TexImage2D(GL_TEXTURE_2D, 0, internal_format,
+                                 dest_rect.width(), dest_rect.height(), 0,
+                                 format, type, nullptr);
+      destination_gl->CopySubTextureCHROMIUM(
+          intermediate_texture, 0, GL_TEXTURE_2D, texture, 0, 0, 0,
+          dest_rect.x(), dest_rect.y(), dest_rect.width(), dest_rect.height(),
+          flip_y, premultiply_alpha, false);
+    } else {
+      destination_gl->CopyTextureCHROMIUM(
+          intermediate_texture, 0, GL_TEXTURE_2D, texture, 0, internal_format,
+          type, flip_y, premultiply_alpha, false);
+    }
+
     destination_gl->DeleteTextures(1, &intermediate_texture);
 
     // Wait for destination context to consume mailbox before deleting it in
@@ -855,8 +915,9 @@
     SyncTokenClientImpl client(canvas_gl);
     video_frame->UpdateReleaseSyncToken(&client);
   } else {
-    CopyVideoFrameSingleTextureToGLTexture(destination_gl, video_frame.get(),
-                                           texture, premultiply_alpha, flip_y);
+    CopyVideoFrameSingleTextureToGLTexture(
+        destination_gl, video_frame.get(), SingleFrameForWebGL, texture,
+        internal_format, format, type, premultiply_alpha, flip_y);
   }
 
   return true;
diff --git a/media/renderers/skcanvas_video_renderer.h b/media/renderers/skcanvas_video_renderer.h
index 3831139..1f3d2f7f 100644
--- a/media/renderers/skcanvas_video_renderer.h
+++ b/media/renderers/skcanvas_video_renderer.h
@@ -64,16 +64,25 @@
                                            void* rgb_pixels,
                                            size_t row_bytes);
 
+  enum SingleFrameCopyMode {
+    SingleFrameForVideoElementOrCanvas,
+    SingleFrameForWebGL
+  };
+
   // Copy the contents of texture of |video_frame| to texture |texture|.
   // |level|, |internal_format|, |type| specify target texture |texture|.
   // The format of |video_frame| must be VideoFrame::NATIVE_TEXTURE.
-  // Assumes |texture| has already been allocated with the appropriate
-  // size and a compatible format, internal format and type; this is
-  // effectively a "TexSubImage" operation.
+  // |copy_mode| alters how the copy is done, and takes into consideration
+  // whether the caller will clip the texture to the frame's |visible_rect|,
+  // or expects this to be done internally.
   static void CopyVideoFrameSingleTextureToGLTexture(
       gpu::gles2::GLES2Interface* gl,
       VideoFrame* video_frame,
+      SingleFrameCopyMode copy_mode,
       unsigned int texture,
+      unsigned int internal_format,
+      unsigned int format,
+      unsigned int type,
       bool premultiply_alpha,
       bool flip_y);
 
@@ -82,15 +91,15 @@
   // |level|, |internal_format|, |type| specify target texture |texture|.
   // The format of |video_frame| must be VideoFrame::NATIVE_TEXTURE.
   // |context_3d| has a GrContext that may be used during the copy.
-  // Assumes |texture| has already been allocated with the appropriate
-  // size and a compatible format, internal format and type; this is
-  // effectively a "TexSubImage" operation.
   // Returns true on success.
   bool CopyVideoFrameTexturesToGLTexture(
       const Context3D& context_3d,
       gpu::gles2::GLES2Interface* destination_gl,
       const scoped_refptr<VideoFrame>& video_frame,
       unsigned int texture,
+      unsigned int internal_format,
+      unsigned int format,
+      unsigned int type,
       bool premultiply_alpha,
       bool flip_y);
 
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index 6a146e7a..3f781a7 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -26,12 +26,14 @@
   deps = [
     "//base",
     "//device/battery:mojo_bindings",
+    "//device/generic_sensor",
     "//device/sensors",
     "//device/sensors/public/interfaces",
     "//device/vibration:mojo_bindings",
     "//device/wake_lock",
     "//services/device/fingerprint",
     "//services/device/power_monitor",
+    "//services/device/public/cpp:device_features",
     "//services/device/screen_orientation",
     "//services/device/time_zone_monitor",
     "//services/service_manager/public/cpp",
diff --git a/services/device/device_service.cc b/services/device/device_service.cc
index 2535dad..ea0f021 100644
--- a/services/device/device_service.cc
+++ b/services/device/device_service.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
@@ -14,11 +15,13 @@
 #include "device/battery/battery_monitor.mojom.h"
 #include "device/battery/battery_monitor_impl.h"
 #include "device/battery/battery_status_service.h"
+#include "device/generic_sensor/sensor_provider_impl.h"
 #include "device/sensors/device_sensor_host.h"
 #include "device/wake_lock/wake_lock_context_provider.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "services/device/fingerprint/fingerprint.h"
 #include "services/device/power_monitor/power_monitor_message_broadcaster.h"
+#include "services/device/public/cpp/device_features.h"
 #include "services/device/time_zone_monitor/time_zone_monitor.h"
 #include "services/service_manager/public/cpp/connection.h"
 #include "services/service_manager/public/cpp/interface_registry.h"
@@ -91,6 +94,9 @@
   registry_.AddInterface<mojom::OrientationAbsoluteSensor>(this);
   registry_.AddInterface<mojom::PowerMonitor>(this);
   registry_.AddInterface<mojom::ScreenOrientationListener>(this);
+  if (base::FeatureList::IsEnabled(features::kGenericSensor)) {
+    registry_.AddInterface<mojom::SensorProvider>(this);
+  }
   registry_.AddInterface<mojom::TimeZoneMonitor>(this);
   registry_.AddInterface<mojom::WakeLockContextProvider>(this);
 
@@ -218,6 +224,15 @@
 }
 
 void DeviceService::Create(const service_manager::Identity& remote_identity,
+                           mojom::SensorProviderRequest request) {
+  if (io_task_runner_) {
+    io_task_runner_->PostTask(
+        FROM_HERE, base::Bind(&device::SensorProviderImpl::Create,
+                              file_task_runner_, base::Passed(&request)));
+  }
+}
+
+void DeviceService::Create(const service_manager::Identity& remote_identity,
                            mojom::TimeZoneMonitorRequest request) {
   if (!time_zone_monitor_)
     time_zone_monitor_ = TimeZoneMonitor::Create(file_task_runner_);
diff --git a/services/device/device_service.h b/services/device/device_service.h
index 5b66d3e..cd38c236 100644
--- a/services/device/device_service.h
+++ b/services/device/device_service.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "device/battery/battery_monitor.mojom.h"
+#include "device/generic_sensor/public/interfaces/sensor_provider.mojom.h"
 #include "device/screen_orientation/public/interfaces/screen_orientation.mojom.h"
 #include "device/sensors/public/interfaces/light.mojom.h"
 #include "device/sensors/public/interfaces/motion.mojom.h"
@@ -60,6 +61,7 @@
       public service_manager::InterfaceFactory<mojom::PowerMonitor>,
       public service_manager::InterfaceFactory<
           mojom::ScreenOrientationListener>,
+      public service_manager::InterfaceFactory<mojom::SensorProvider>,
       public service_manager::InterfaceFactory<mojom::TimeZoneMonitor>,
       public service_manager::InterfaceFactory<mojom::WakeLockContextProvider> {
  public:
@@ -117,6 +119,10 @@
   void Create(const service_manager::Identity& remote_identity,
               mojom::ScreenOrientationListenerRequest request) override;
 
+  // InterfaceFactory<mojom::SensorProvider>:
+  void Create(const service_manager::Identity& remote_identity,
+              mojom::SensorProviderRequest request) override;
+
   // InterfaceFactory<mojom::TimeZoneMonitor>:
   void Create(const service_manager::Identity& remote_identity,
               mojom::TimeZoneMonitorRequest request) override;
diff --git a/services/device/manifest.json b/services/device/manifest.json
index 78b521e..6b7a1b0d 100644
--- a/services/device/manifest.json
+++ b/services/device/manifest.json
@@ -6,6 +6,7 @@
       "provides": {
         "device:battery_monitor": [ "device::mojom::BatteryMonitor" ],
         "device:fingerprint": [ "device::mojom::Fingerprint" ],
+        "device:generic_sensor": [ "device::mojom::SensorProvider" ],
         "device:power_monitor": [ "device::mojom::PowerMonitor" ],
         "device:screen_orientation": [ "device::mojom::ScreenOrientationListener" ],
         "device:sensors": [
diff --git a/services/device/public/cpp/BUILD.gn b/services/device/public/cpp/BUILD.gn
new file mode 100644
index 0000000..e9ebe27
--- /dev/null
+++ b/services/device/public/cpp/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+
+source_set("device_features") {
+  public = [
+    "device_features.h",
+  ]
+  sources = [
+    "device_features.cc",
+  ]
+  public_deps = [
+    "//base",
+  ]
+}
diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
new file mode 100644
index 0000000..ab4b9ba
--- /dev/null
+++ b/services/device/public/cpp/device_features.cc
@@ -0,0 +1,14 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/device/public/cpp/device_features.h"
+
+namespace features {
+
+// Enables sensors based on Generic Sensor API:
+// https://w3c.github.io/sensors/
+const base::Feature kGenericSensor{"GenericSensor",
+                                   base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace features
diff --git a/services/device/public/cpp/device_features.h b/services/device/public/cpp/device_features.h
new file mode 100644
index 0000000..b10a16b
--- /dev/null
+++ b/services/device/public/cpp/device_features.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines all the public base::FeatureList features for the
+// services/device module.
+
+#ifndef SERVICES_DEVICE_PUBLIC_CPP_DEVICE_FEATURES_H_
+#define SERVICES_DEVICE_PUBLIC_CPP_DEVICE_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace features {
+
+// The features should be documented alongside the definition of their values
+// in the .cc file.
+extern const base::Feature kGenericSensor;
+
+}  // namespace features
+
+#endif  // SERVICES_DEVICE_PUBLIC_CPP_DEVICE_FEATURES_H_
diff --git a/sql/OWNERS b/sql/OWNERS
index 77090f0a..9a8c2d2 100644
--- a/sql/OWNERS
+++ b/sql/OWNERS
@@ -1,2 +1,2 @@
-shess@chromium.org
-gbillock@chromium.org
+michaeln@chromium.org
+pwnall@chromium.org
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 2e42f21..4276eb1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -12931,6 +12931,7 @@
               "os": "Ubuntu-14.04"
             }
           ],
+          "hard_timeout": 900,
           "shards": 6
         }
       }
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index dc24637a..0e84620 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1703,9 +1703,6 @@
 
 crbug.com/605059 [ Retina ] fast/text/international/rtl-negative-letter-spacing.html [ Failure ]
 
-crbug.com/704077 paint/invalidation/svg/feImage-target-attribute-change-with-use-indirection-2.svg [ NeedsRebaseline ]
-crbug.com/704077 paint/invalidation/svg/marker-viewBox-changes.svg [ NeedsRebaseline ]
-
 crbug.com/610464 [ Linux Win7 Debug ] inspector/components/throttler.html [ Failure Pass ]
 crbug.com/487344 [ Linux Mac Win7 ] paint/invalidation/video-paint-invalidation.html [ Failure ]
 crbug.com/487344 [ Linux Mac Win7 ] virtual/disable-spinvalidation/paint/invalidation/video-paint-invalidation.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/editing/deleting/delete-br-013.html b/third_party/WebKit/LayoutTests/editing/deleting/delete-br-013.html
index 324940c..0bb24d43 100644
--- a/third_party/WebKit/LayoutTests/editing/deleting/delete-br-013.html
+++ b/third_party/WebKit/LayoutTests/editing/deleting/delete-br-013.html
@@ -12,6 +12,6 @@
     <body onload="test()">
         <div>This tests that we only preserve an empty paragraph's style when moving paragraphs around if the selection is still in an empty paragraph after the move occurs.</div>
         <div>The test passes if the text below is still underlined.</div><br>
-        <div contenteditable><span class="Apple-style-span" style="text-decoration: underline;">This text should be underlined</span><div id="dv"><br></div></div>
+        <div contenteditable><span style="text-decoration: underline;">This text should be underlined</span><div id="dv"><br></div></div>
     </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style-expected.txt b/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style-expected.txt
index 54a4bd5..6d7aac4 100644
--- a/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style-expected.txt
+++ b/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style-expected.txt
@@ -3,7 +3,6 @@
 "
 | <div>
 |   <font>
-|     class="Apple-style-span"
 |     face="monospace"
 |     "hello world<#selection-caret>"
 |   <span>
diff --git a/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style.html b/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style.html
index dcc2ded..61314b30 100644
--- a/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style.html
+++ b/third_party/WebKit/LayoutTests/editing/deleting/delete-line-break-between-paragraphs-with-same-style.html
@@ -2,7 +2,7 @@
 <html>
 <body>
 <div id="test" contenteditable>
-<div><font class="Apple-style-span" face="monospace">hello world</font></div><div><font class="Apple-style-span" face="monospace">WebKit</font></div>
+<div><font face="monospace">hello world</font></div><div><font face="monospace">WebKit</font></div>
 </div>
 <script src="../../resources/dump-as-markup.js"></script>
 <script>
diff --git a/third_party/WebKit/LayoutTests/editing/execCommand/19087.html b/third_party/WebKit/LayoutTests/editing/execCommand/19087.html
index 6f55488f..1f39f2eb1 100644
--- a/third_party/WebKit/LayoutTests/editing/execCommand/19087.html
+++ b/third_party/WebKit/LayoutTests/editing/execCommand/19087.html
@@ -14,7 +14,7 @@
     <div id="description">This tests for a crash when indenting a particular selection that contains a block nested in an inline element that contains an hr.  It should not crash.</div>
     <blockquote id="start" class="webkit-indent-blockquote" style="margin: 0 0 0 40px; border: none; padding: 0px;">
         <h5>
-            <span class="Apple-style-span" style="font-size: 16px;">
+            <span style="font-size: 16px;">
                 <hr style="text-align: right;">
                 <div style="text-align: right;">
                     <br>
diff --git a/third_party/WebKit/LayoutTests/editing/execCommand/indent-paragraphs.html b/third_party/WebKit/LayoutTests/editing/execCommand/indent-paragraphs.html
index 3e16407..89b7339b 100644
--- a/third_party/WebKit/LayoutTests/editing/execCommand/indent-paragraphs.html
+++ b/third_party/WebKit/LayoutTests/editing/execCommand/indent-paragraphs.html
@@ -8,7 +8,7 @@
     '<div contenteditable>',
       '^<div>',
         '<div>',
-          '<span class="Apple-style-span" style="background-color: red;">',
+          '<span style="background-color: red;">',
             'Hello <img src="http://"> world',
           '</span>',
         '</div>',
@@ -23,7 +23,7 @@
       '<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">',
         '<div>',
           '<div>',
-            '<span class="Apple-style-span" style="background-color: red;">',
+            '<span style="background-color: red;">',
               '^Hello <img src="http://"> world',
             '</span>',
           '</div>',
diff --git a/third_party/WebKit/LayoutTests/editing/execCommand/indent/indent_with_style.html b/third_party/WebKit/LayoutTests/editing/execCommand/indent/indent_with_style.html
index afecb1a2..3bc3617 100644
--- a/third_party/WebKit/LayoutTests/editing/execCommand/indent/indent_with_style.html
+++ b/third_party/WebKit/LayoutTests/editing/execCommand/indent/indent_with_style.html
@@ -9,21 +9,21 @@
     assert_selection(
         [
             '<div contenteditable>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">one</span></div>',
-            '<div>^<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">two</span></div>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">three</span>|</div>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">four</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">one</span></div>',
+            '<div>^<span style="background-color: rgb(255, 0, 0);">two</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">three</span>|</div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">four</span></div>',
             '</div>',
         ].join(''),
         'Indent',
         [
             '<div contenteditable>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">one</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">one</span></div>',
             '<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">^two</span></div>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">three|</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">^two</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">three|</span></div>',
             '</blockquote>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">four</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">four</span></div>',
             '</div>',
         ].join(''),
         'https://wkb.ug/23995');
@@ -31,22 +31,22 @@
     assert_selection(
         [
             '<div contenteditable>|one',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">two</span> three</div>',
-            '<div>four<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">five</span></div>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">four</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">two</span> three</div>',
+            '<div>four<span style="background-color: rgb(255, 0, 0);">five</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">four</span></div>',
             '<ul><li><b>foo</b>bar</li></ul>',
-            '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);"><font class="Apple-style-span" color="#3333FF">five</font></span> six <b><i>seven</i></b>',
+            '<span style="background-color: rgb(255, 0, 0);"><font color="#3333FF">five</font></span> six <b><i>seven</i></b>',
             '</div>',
         ].join(''),
         'Indent',
         [
             '<div contenteditable>',
             '<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">|one</blockquote>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">two</span> three</div>',
-            '<div>four<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">five</span></div>',
-            '<div><span class="Apple-style-span" style="background-color: rgb(255, 0, 0);">four</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">two</span> three</div>',
+            '<div>four<span style="background-color: rgb(255, 0, 0);">five</span></div>',
+            '<div><span style="background-color: rgb(255, 0, 0);">four</span></div>',
             '<ul><li><b>foo</b>bar</li></ul>',
-            '<span class="Apple-style-span" style="background-color: rgb(255, 0, 0);"><font class="Apple-style-span" color="#3333FF">five</font></span> six <b><i>seven</i></b>',
+            '<span style="background-color: rgb(255, 0, 0);"><font color="#3333FF">five</font></span> six <b><i>seven</i></b>',
             '</div>',
         ].join(''),
         'http://wkb.ug/32233 http://wkb.ug/32843');
diff --git a/third_party/WebKit/LayoutTests/editing/execCommand/insertHTML-mutation-crash.html b/third_party/WebKit/LayoutTests/editing/execCommand/insertHTML-mutation-crash.html
index 2c37795..db38a27 100644
--- a/third_party/WebKit/LayoutTests/editing/execCommand/insertHTML-mutation-crash.html
+++ b/third_party/WebKit/LayoutTests/editing/execCommand/insertHTML-mutation-crash.html
@@ -18,14 +18,14 @@
                     el.lastElementChild.appendChild(el.firstElementChild);
                     el.lastElementChild && el.removeChild(el.lastElementChild);
                 }
-                if (e.target.firstChild && e.target.firstChild.className == 'Apple-style-span')
+                if (e.target.firstChild && e.target.firstChild.className == 'foo')
                     e.target.firstChild.innerHTML = e.target.firstChild.innerHTML.split(' ')[0];
             };
             document.addEventListener("DOMSubtreeModified", listener);
 
             var el = document.getElementById('cont');
             window.getSelection().setBaseAndExtent(document.getElementById('start'), 0, document.getElementById('end'), 0);
-            var str = '<span class="Apple-style-span" style="color: red;"><span>styled</span> <span>content</span></span>';
+            var str = '<span class="foo" style="color: red;"><span>styled</span> <span>content</span></span>';
             document.execCommand("InsertHTML", false, str);
 
             document.removeEventListener("DOMSubtreeModified", listener);
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/4744008.html b/third_party/WebKit/LayoutTests/editing/pasteboard/4744008.html
index a9ef81d7..4bb9fb3 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/4744008.html
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/4744008.html
@@ -13,5 +13,5 @@
 var sel = window.getSelection();
 
 sel.collapse(div, 0);
-document.execCommand("InsertHTML", false, "foo <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><font><span class='Apple-style-span'></span></font>bar");
+document.execCommand("InsertHTML", false, "foo <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><font><span></span></font>bar");
 </script>
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/5078739.html b/third_party/WebKit/LayoutTests/editing/pasteboard/5078739.html
index e98797dc..dbad2a82 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/5078739.html
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/5078739.html
@@ -7,5 +7,5 @@
     
 var div = document.getElementById("div");
 div.focus();
-document.execCommand("InsertHTML", false, "<span class='Apple-style-span' style='border-collapse: separate; color: rgb(0, 0, 0); font-family: Times; font-size: 16px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-fill-color: rgb(0, 0, 0); -webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: 0; '></span>");
+document.execCommand("InsertHTML", false, "<span style='border-collapse: separate; color: rgb(0, 0, 0); font-family: Times; font-size: 16px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-fill-color: rgb(0, 0, 0); -webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: 0; '></span>");
 </script>
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph-expected.txt b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph-expected.txt
index 1f5dc60..5c4f6a6 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph-expected.txt
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph-expected.txt
@@ -5,14 +5,12 @@
 | "
 "
 | <font>
-|   class="Apple-style-span"
 |   face="'courier new', monospace"
 |   <div>
 |     style="font-family: arial;"
 |     "
 "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       "hello"
 |   "
@@ -20,7 +18,6 @@
 |   <div>
 |     style="font-family: arial; "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       " WebKit"
 | "
@@ -30,14 +27,12 @@
 | "
 "
 | <font>
-|   class="Apple-style-span"
 |   face="'courier new', monospace"
 |   <div>
 |     style="font-family: arial;"
 |     "
 "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       "hello"
 |   "
@@ -45,7 +40,6 @@
 |   <div>
 |     style="font-family: arial; "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       " WebKit"
 | "
@@ -55,14 +49,12 @@
 | "
 "
 | <font>
-|   class="Apple-style-span"
 |   face="'courier new', monospace"
 |   <div>
 |     style="font-family: arial;"
 |     "
 "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       "hello"
 |   "
@@ -70,7 +62,6 @@
 |   <div>
 |     style="font-family: arial; "
 |     <span>
-|       class="Apple-style-span"
 |       style="font-family: 'courier new', monospace;"
 |       " WebKit"
 | "
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph.html b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph.html
index 254f03a..d4b8b60 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph.html
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-delete-insertion-position-skip-paragraph.html
@@ -4,9 +4,9 @@
 <p id="description">This test ensures WebKit does not skip paragraphs when determining the insertion position for paste.<br>
 You should see "hello world&lt;caret&gt; WebKit" below.</p>
 <div id="test" contenteditable>
-<font class="Apple-style-span" face="'courier new', monospace"><div style="font-family: arial;">
-<span class="Apple-style-span" style="font-family: 'courier new', monospace;">hello</span></div>
-<div style="font-family: arial; "><span class="Apple-style-span" style="font-family: 'courier new', monospace;">&nbsp;WebKit</span></div></font>
+<font face="'courier new', monospace"><div style="font-family: arial;">
+<span style="font-family: 'courier new', monospace;">hello</span></div>
+<div style="font-family: arial; "><span style="font-family: 'courier new', monospace;">&nbsp;WebKit</span></div></font>
 </div>
 <script src="../editing.js"></script>
 <script src="../../resources/dump-as-markup.js"></script>
@@ -17,7 +17,7 @@
 moveSelectionForwardByLineBoundaryCommand();
 Markup.dump('test', 'Initial markup');
 
-insertHTMLCommand('<span class="Apple-style-span" style="background-color: #fee;">&nbsp;world</span>');
+insertHTMLCommand('<span style="background-color: #fee;">&nbsp;world</span>');
 Markup.dump('test', 'After inserting " world"');
 
 for (var i = 0; i < 5; i++) // Make sure we're at the very end.
diff --git a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-into-blockquote-with-document-font-color.html b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-into-blockquote-with-document-font-color.html
index 7ea7c35c..4d3122f 100644
--- a/third_party/WebKit/LayoutTests/editing/pasteboard/paste-into-blockquote-with-document-font-color.html
+++ b/third_party/WebKit/LayoutTests/editing/pasteboard/paste-into-blockquote-with-document-font-color.html
@@ -21,7 +21,7 @@
 var s = window.getSelection();
 s.collapse(edit.firstChild.firstChild.firstChild, 1);
 
-document.execCommand("InsertHTML", false, "<span class='Apple-style-span' style='color: black;'>This text should be blue.</span>");
+document.execCommand("InsertHTML", false, "<span style='color: black;'>This text should be blue.</span>");
 
 Markup.description(description.textContent);
 Markup.dump('edit');
diff --git a/third_party/WebKit/LayoutTests/editing/selection/button-right-click.html b/third_party/WebKit/LayoutTests/editing/selection/button-right-click.html
index e2d7180c..c961634 100644
--- a/third_party/WebKit/LayoutTests/editing/selection/button-right-click.html
+++ b/third_party/WebKit/LayoutTests/editing/selection/button-right-click.html
@@ -3,7 +3,7 @@
 <title>Right clicking on the button should not crash.</title> 
 </head> 
 <body>
-<span class="Apple-style-span" style="font-family: Tahoma, Arial; font-size: 13px;">
+<span style="font-family: Tahoma, Arial; font-size: 13px;">
 <button type="button" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-style: initial; border-color: initial; padding-top: 0.2em; padding-right: 0.2em; padding-bottom: 0.2em; padding-left: 0.2em; line-height: normal; font: inherit; color: inherit; white-space: nowrap; background-repeat: repeat-x; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: rgb(222, 222, 222); border-right-color: rgb(222, 222, 222); border-bottom-color: rgb(222, 222, 222); border-left-color: rgb(222, 222, 222); cursor: pointer; vertical-align: middle; text-align: center; -webkit-background-clip: initial; -webkit-background-origin: initial; background-color: rgb(255, 255, 255); -webkit-user-select: none; background-position: 0% 0%; ">
     <div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; line-height: normal; font: inherit; color: inherit; display: inline-block; border-style: initial; border-color: initial; vertical-align: middle; "></div>
     <div id="test" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0.3em; padding-bottom: 0px; padding-left: 0.3em; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; line-height: normal; font: inherit; color: inherit; display: inline-block; border-style: initial; border-color: initial; vertical-align: middle; ">Color</div>
diff --git a/third_party/WebKit/LayoutTests/editing/style/make-text-writing-direction-inline-mac-expected.txt b/third_party/WebKit/LayoutTests/editing/style/make-text-writing-direction-inline-mac-expected.txt
index 89b53f0..6000ea4a 100644
--- a/third_party/WebKit/LayoutTests/editing/style/make-text-writing-direction-inline-mac-expected.txt
+++ b/third_party/WebKit/LayoutTests/editing/style/make-text-writing-direction-inline-mac-expected.txt
@@ -9,9 +9,9 @@
 PASS Natural on second and third words of "<b>hello world</b> webkit"
 PASS LeftToRight on second and third words of "<b>hello world</b> webkit"
 PASS RightToLeft on second and third words of "<b>hello world</b> webkit"
-FAIL (due to the bug 44359) Natural on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span></span><span><span> webkit</span></span><span dir="rtl"><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span></span> webkit<span dir="rtl"><span dir="ltr"> rocks</span></span>
-FAIL (due to the bug 44359) LeftToRight on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span></span><span><span style="unicode-bidi: isolate;"> webkit</span></span><span dir="rtl"><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span></span><span style="unicode-bidi: isolate;"> webkit</span><span dir="rtl"><span dir="ltr"> rocks</span></span>
-FAIL (due to the bug 44359) RightToLeft on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span><span> webkit</span><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span> webkit<span dir="ltr"> rocks</span></span>
+FAIL Natural on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span></span><span><span> webkit</span></span><span dir="rtl"><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span></span> webkit<span dir="rtl"><span dir="ltr"> rocks</span></span>
+FAIL LeftToRight on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span></span><span><span style="unicode-bidi: isolate;"> webkit</span></span><span dir="rtl"><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span></span><span style="unicode-bidi: isolate;"> webkit</span><span dir="rtl"><span dir="ltr"> rocks</span></span>
+FAIL RightToLeft on third word of "<span dir="rtl">hello <span dir="ltr">world webkit rocks</span></span>" yielded <span dir="rtl">hello <span dir="ltr">world</span><span> webkit</span><span dir="ltr"> rocks</span></span> but expected <span dir="rtl">hello <span dir="ltr">world</span> webkit<span dir="ltr"> rocks</span></span>
 PASS Natural on first word of "هنا يكتب النص العربي"
 PASS LeftToRight on first word of "هنا يكتب النص العربي"
 PASS RightToLeft on first word of "هنا يكتب النص العربي"
@@ -24,7 +24,7 @@
 PASS Natural on second and third words of "<div dir="rtl"><b>هنا يكتب</b> النص العربي</div>"
 PASS LeftToRight on second and third words of "<div dir="rtl"><b>هنا يكتب</b> النص العربي</div>"
 PASS RightToLeft on second and third words of "<div dir="rtl"><b>هنا يكتب</b> النص العربي</div>"
-FAIL (due to the bug 44359) Natural on third word of "<div dir="rtl">هنا <span dir="ltr">يكتب النص العربي</span></div>" yielded <div dir="rtl">هنا <span dir="ltr">يكتب</span><span> النص</span><span dir="ltr"> العربي</span></div> but expected <div dir="rtl">هنا <span dir="ltr">يكتب</span> النص<span dir="ltr"> العربي</span></div>
+FAIL Natural on third word of "<div dir="rtl">هنا <span dir="ltr">يكتب النص العربي</span></div>" yielded <div dir="rtl">هنا <span dir="ltr">يكتب</span><span> النص</span><span dir="ltr"> العربي</span></div> but expected <div dir="rtl">هنا <span dir="ltr">يكتب</span> النص<span dir="ltr"> العربي</span></div>
 PASS LeftToRight on third word of "<div dir="rtl">هنا <span dir="ltr">يكتب النص العربي</span></div>"
 PASS RightToLeft on third word of "<div dir="rtl">هنا <span dir="ltr">يكتب النص العربي</span></div>"
 PASS Natural on first word of "写中文"
diff --git a/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-mac.js b/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-mac.js
index a51db60..b1f8094 100644
--- a/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-mac.js
+++ b/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-mac.js
@@ -29,15 +29,14 @@
     var selected = selector(testContainer);
     window.testRunner.execCommand('MakeTextWritingDirection' + command);
 
-    // Remove Apple-style-span because it does not need to be tested here.
-    var actual = testContainer.innerHTML.replace(/ class="Apple-style-span"/g, '');
+    var actual = testContainer.innerHTML;
     var action = command + ' on ' + selected + ' of "' + content + '"';
     if (actual == expected)
         testPassed(action);
     else {
         error = ' yielded ' + actual + ' but expected ' + expected;
         recursivelyRemoveExtraenousSpan(testContainer);
-        if (testContainer.innerHTML.replace(/ class="Apple-style-span"/g, '') == expected)
+        if (testContainer.innerHTM == expected)
             testFailed('(due to the bug 44359) ' + action + error);
         else
             testFailed(action + error);
diff --git a/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-win.js b/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-win.js
index 17ed903..ea51b8e 100644
--- a/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-win.js
+++ b/third_party/WebKit/LayoutTests/editing/style/script-tests/make-text-writing-direction-inline-win.js
@@ -29,15 +29,14 @@
     var selected = selector(testContainer);
     window.testRunner.execCommand('MakeTextWritingDirection' + command);
 
-    // Remove Apple-style-span because it does not need to be tested here.
-    var actual = testContainer.innerHTML.replace(/ class="Apple-style-span"/g, '');
+    var actual = testContainer.innerHTML;
     var action = command + ' on ' + selected + ' of "' + content + '"';
     if (actual == expected)
         testPassed(action);
     else {
         error = ' yielded ' + actual + ' but expected ' + expected;
         recursivelyRemoveExtraenousSpan(testContainer);
-        if (testContainer.innerHTML.replace(/ class="Apple-style-span"/g, '') == expected)
+        if (testContainer.innerHTML == expected)
             testFailed('(due to the bug 44359) ' + action + error);
         else
             testFailed(action + error);
diff --git a/third_party/WebKit/LayoutTests/sensor/resources/sensor-helpers.js b/third_party/WebKit/LayoutTests/sensor/resources/sensor-helpers.js
index 0a9949d..c1178c4 100644
--- a/third_party/WebKit/LayoutTests/sensor/resources/sensor-helpers.js
+++ b/third_party/WebKit/LayoutTests/sensor/resources/sensor-helpers.js
@@ -23,7 +23,8 @@
     'mojo/public/js/bindings',
     'device/generic_sensor/public/interfaces/sensor_provider.mojom',
     'device/generic_sensor/public/interfaces/sensor.mojom',
-  ], (core, bindings, sensor_provider, sensor) => {
+    'services/device/public/interfaces/constants.mojom',
+  ], (core, bindings, sensor_provider, sensor, deviceConstants) => {
 
     // Helper function that returns resolved promise with result.
     function sensorResponse(success) {
@@ -350,7 +351,8 @@
     }
 
     let mockSensorProvider = new MockSensorProvider;
-    mojo.frameInterfaces.addInterfaceOverrideForTesting(
+    mojo.connector.addInterfaceOverrideForTesting(
+        deviceConstants.kServiceName,
         sensor_provider.SensorProvider.name,
         pipe => {
           mockSensorProvider.bindToPipe(pipe);
diff --git a/third_party/WebKit/Source/core/html/HTMLVideoElement.cpp b/third_party/WebKit/Source/core/html/HTMLVideoElement.cpp
index 3bf2a9d..2e741f235 100644
--- a/third_party/WebKit/Source/core/html/HTMLVideoElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLVideoElement.cpp
@@ -310,13 +310,16 @@
 bool HTMLVideoElement::CopyVideoTextureToPlatformTexture(
     gpu::gles2::GLES2Interface* gl,
     GLuint texture,
+    GLenum internal_format,
+    GLenum format,
+    GLenum type,
     bool premultiply_alpha,
     bool flip_y) {
   if (!GetWebMediaPlayer())
     return false;
 
   return GetWebMediaPlayer()->CopyVideoTextureToPlatformTexture(
-      gl, texture, premultiply_alpha, flip_y);
+      gl, texture, internal_format, format, type, premultiply_alpha, flip_y);
 }
 
 bool HTMLVideoElement::TexImageImpl(
diff --git a/third_party/WebKit/Source/core/html/HTMLVideoElement.h b/third_party/WebKit/Source/core/html/HTMLVideoElement.h
index bc7e7fa..6f14c678 100644
--- a/third_party/WebKit/Source/core/html/HTMLVideoElement.h
+++ b/third_party/WebKit/Source/core/html/HTMLVideoElement.h
@@ -75,9 +75,11 @@
   void PaintCurrentFrame(PaintCanvas*, const IntRect&, const PaintFlags*) const;
 
   // Used by WebGL to do GPU-GPU textures copy if possible.
-  // The caller is responsible for allocating the destination texture.
   bool CopyVideoTextureToPlatformTexture(gpu::gles2::GLES2Interface*,
                                          GLuint texture,
+                                         GLenum internal_format,
+                                         GLenum format,
+                                         GLenum type,
                                          bool premultiply_alpha,
                                          bool flip_y);
 
diff --git a/third_party/WebKit/Source/modules/sensor/BUILD.gn b/third_party/WebKit/Source/modules/sensor/BUILD.gn
index ae8ffaed..9660eb03 100644
--- a/third_party/WebKit/Source/modules/sensor/BUILD.gn
+++ b/third_party/WebKit/Source/modules/sensor/BUILD.gn
@@ -34,5 +34,7 @@
     "//device/base/synchronization",
     "//device/generic_sensor/public/cpp",
     "//device/generic_sensor/public/interfaces:interfaces_blink",
+    "//services/device/public/interfaces:constants_blink",
+    "//services/service_manager/public/cpp",
   ]
 }
diff --git a/third_party/WebKit/Source/modules/sensor/DEPS b/third_party/WebKit/Source/modules/sensor/DEPS
index 97e943c..1ba448a5 100644
--- a/third_party/WebKit/Source/modules/sensor/DEPS
+++ b/third_party/WebKit/Source/modules/sensor/DEPS
@@ -7,4 +7,5 @@
     "+modules/ModulesExport.h",
     "+modules/sensor",
     "+mojo/public/cpp/bindings",
+    "+services/device/public/interfaces/constants.mojom-blink.h",
 ]
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
index b4858ea..8597e12d 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
+++ b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.cpp
@@ -6,8 +6,9 @@
 
 #include "modules/sensor/SensorProxy.h"
 #include "platform/mojo/MojoHelper.h"
-#include "public/platform/InterfaceProvider.h"
 #include "public/platform/Platform.h"
+#include "services/device/public/interfaces/constants.mojom-blink.h"
+#include "services/service_manager/public/cpp/connector.h"
 
 namespace blink {
 
@@ -15,12 +16,12 @@
 SensorProviderProxy::SensorProviderProxy(LocalFrame& frame)
     : Supplement<LocalFrame>(frame) {}
 
-void SensorProviderProxy::InitializeIfNeeded(LocalFrame* frame) {
+void SensorProviderProxy::InitializeIfNeeded() {
   if (IsInitialized())
     return;
 
-  frame->GetInterfaceProvider()->GetInterface(
-      mojo::MakeRequest(&sensor_provider_));
+  Platform::Current()->GetConnector()->BindInterface(
+      device::mojom::blink::kServiceName, mojo::MakeRequest(&sensor_provider_));
   sensor_provider_.set_connection_error_handler(ConvertToBaseCallback(
       WTF::Bind(&SensorProviderProxy::OnSensorProviderConnectionError,
                 WrapWeakPersistent(this))));
@@ -39,7 +40,7 @@
     provider_proxy = new SensorProviderProxy(*frame);
     Supplement<LocalFrame>::ProvideTo(*frame, SupplementName(), provider_proxy);
   }
-  provider_proxy->InitializeIfNeeded(frame);
+  provider_proxy->InitializeIfNeeded();
   return provider_proxy;
 }
 
diff --git a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
index eea1338..853b934 100644
--- a/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
+++ b/third_party/WebKit/Source/modules/sensor/SensorProviderProxy.h
@@ -39,7 +39,7 @@
 
   explicit SensorProviderProxy(LocalFrame&);
   static const char* SupplementName();
-  void InitializeIfNeeded(LocalFrame*);
+  void InitializeIfNeeded();
   bool IsInitialized() const { return sensor_provider_; }
 
   device::mojom::blink::SensorProvider* GetSensorProvider() const {
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp
index 38c7333..a62f158 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp
+++ b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.cpp
@@ -4914,7 +4914,8 @@
                                  SentinelEmptyRect(), 1, 0, exception_state);
 }
 
-bool WebGLRenderingContextBase::CanUseTexImageByGPU(GLenum type) {
+bool WebGLRenderingContextBase::CanUseTexImageByGPU(GLenum format,
+                                                    GLenum type) {
 #if OS(MACOSX)
   // RGB5_A1 is not color-renderable on NVIDIA Mac, see crbug.com/676209.
   // Though, glCopyTextureCHROMIUM can handle RGB5_A1 internalformat by doing a
@@ -4924,6 +4925,14 @@
   if (type == GL_UNSIGNED_SHORT_5_5_5_1)
     return false;
 #endif
+  // TODO(kbr): bugs were observed when using CopyTextureCHROMIUM to
+  // copy hardware-accelerated video textures to red-channel textures.
+  // These bugs were seen on macOS but may indicate more general
+  // problems. Investigate the root cause of this and fix it.
+  // crbug.com/710673
+  if (format == GL_RED || format == GL_RED_INTEGER)
+    return false;
+
   // OES_texture_half_float doesn't support HALF_FLOAT_OES type for
   // CopyTexImage/CopyTexSubImage. And OES_texture_half_float doesn't require
   // HALF_FLOAT_OES type texture to be renderable. So, HALF_FLOAT_OES type
@@ -5115,7 +5124,7 @@
     // float/integer/sRGB internal format.
     // TODO(crbug.com/622958): relax the constrains if copyTextureCHROMIUM is
     // upgraded to handle more formats.
-    if (!canvas->IsAccelerated() || !CanUseTexImageByGPU(type)) {
+    if (!canvas->IsAccelerated() || !CanUseTexImageByGPU(format, type)) {
       // 2D canvas has only FrontBuffer.
       TexImageImpl(function_id, target, level, internalformat, xoffset, yoffset,
                    zoffset, format, type,
@@ -5227,9 +5236,10 @@
       source_image_rect == SentinelEmptyRect() ||
       source_image_rect ==
           IntRect(0, 0, video->videoWidth(), video->videoHeight());
-  const bool use_copyTextureCHROMIUM =
-      function_id == kTexImage2D && source_image_rect_is_default &&
-      depth == 1 && GL_TEXTURE_2D == target && CanUseTexImageByGPU(type);
+  const bool use_copyTextureCHROMIUM = function_id == kTexImage2D &&
+                                       source_image_rect_is_default &&
+                                       depth == 1 && GL_TEXTURE_2D == target &&
+                                       CanUseTexImageByGPU(format, type);
   // Format of source video may be 16-bit format, e.g. Y16 format.
   // glCopyTextureCHROMIUM requires the source texture to be in 8-bit format.
   // Converting 16-bits formated source texture to 8-bits formated texture will
@@ -5243,16 +5253,9 @@
     // to system memory if possible.  Otherwise, it will fall back to the normal
     // SW path.
 
-    // Note that neither
-    // HTMLVideoElement::copyVideoTextureToPlatformTexture nor
-    // ImageBuffer::copyToPlatformTexture allocate the destination texture
-    // any more.
-    TexImage2DBase(target, level, internalformat, video->videoWidth(),
-                   video->videoHeight(), 0, format, type, nullptr);
-
-    if (video->CopyVideoTextureToPlatformTexture(ContextGL(), texture->Object(),
-                                                 unpack_premultiply_alpha_,
-                                                 unpack_flip_y_)) {
+    if (video->CopyVideoTextureToPlatformTexture(
+            ContextGL(), texture->Object(), internalformat, format, type,
+            unpack_premultiply_alpha_, unpack_flip_y_)) {
       texture->UpdateLastUploadedVideo(video->GetWebMediaPlayer());
       return;
     }
@@ -5296,6 +5299,11 @@
         // This is a straight GPU-GPU copy, any necessary color space conversion
         // was handled in the paintCurrentFrameInContext() call.
 
+        // Note that copyToPlatformTexture no longer allocates the destination
+        // texture.
+        TexImage2DBase(target, level, internalformat, video->videoWidth(),
+                       video->videoHeight(), 0, format, type, nullptr);
+
         if (image_buffer->CopyToPlatformTexture(
                 FunctionIDToSnapshotReason(function_id), ContextGL(), target,
                 texture->Object(), unpack_premultiply_alpha_, unpack_flip_y_,
@@ -5392,7 +5400,7 @@
 
   // TODO(kbr): make this work for sub-rectangles of ImageBitmaps.
   if (function_id != kTexSubImage3D && function_id != kTexImage3D &&
-      bitmap->IsAccelerated() && CanUseTexImageByGPU(type) &&
+      bitmap->IsAccelerated() && CanUseTexImageByGPU(format, type) &&
       !selecting_sub_rectangle) {
     if (function_id == kTexImage2D) {
       TexImage2DBase(target, level, internalformat, width, height, 0, format,
diff --git a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
index 3f92cf2..ee5d953 100644
--- a/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
+++ b/third_party/WebKit/Source/modules/webgl/WebGLRenderingContextBase.h
@@ -1111,7 +1111,7 @@
                      GLint zoffset,
                      CanvasImageSource*,
                      const IntRect& source_sub_rectangle);
-  virtual bool CanUseTexImageByGPU(GLenum type);
+  bool CanUseTexImageByGPU(GLenum format, GLenum type);
 
   virtual WebGLImageConversion::PixelStoreParams GetPackPixelStoreParams();
   virtual WebGLImageConversion::PixelStoreParams GetUnpackPixelStoreParams(
diff --git a/third_party/WebKit/public/platform/WebMediaPlayer.h b/third_party/WebKit/public/platform/WebMediaPlayer.h
index 959631b..4f17e2e 100644
--- a/third_party/WebKit/public/platform/WebMediaPlayer.h
+++ b/third_party/WebKit/public/platform/WebMediaPlayer.h
@@ -177,19 +177,20 @@
 
   // TODO(kbr): remove non-|target| version. crbug.com/349871
   //
-  // Do a GPU-GPU texture copy of the natural size of the current
-  // video frame to |texture|. Caller is responsible for allocating
-  // |texture| with the appropriate size. If the copy is impossible or
-  // fails, it returns false.
+  // Do a GPU-GPU texture copy of the current video frame to |texture|,
+  // reallocating |texture| at the appropriate size with given internal
+  // format, format, and type if necessary. If the copy is impossible
+  // or fails, it returns false.
   virtual bool CopyVideoTextureToPlatformTexture(gpu::gles2::GLES2Interface*,
                                                  unsigned texture,
+                                                 unsigned internal_format,
+                                                 unsigned format,
+                                                 unsigned type,
                                                  bool premultiply_alpha,
                                                  bool flip_y) {
     return false;
   }
 
-  // TODO(kbr): when updating calling code to use this, remove the
-  // |internalFormat| and |type| parameters. crbug.com/349871
   // Do a GPU-GPU textures copy. If the copy is impossible or fails, it returns
   // false.
   virtual bool CopyVideoTextureToPlatformTexture(gpu::gles2::GLES2Interface*,
diff --git a/third_party/libxml/README.chromium b/third_party/libxml/README.chromium
index 0d78eaca..d111b700 100644
--- a/third_party/libxml/README.chromium
+++ b/third_party/libxml/README.chromium
@@ -17,8 +17,6 @@
   chromium-issue-620679.patch: See https://crbug.com/620679#c34
   chromium-issue-628581.patch: See https://crbug.com/628581#c18
   chromium-issue-683629.patch: See https://crbug.com/683629#c9
-  libxml2-2.9.4-security-CVE-2017-7375-xmlParsePEReference-xxe.patch:
-  See https://crbug.com/708434
   libxml2-2.9.4-security-CVE-2017-7376-nanohttp-out-of-bounds-write.patch:
   See https://crbug.com/708433
   libxml2-2.9.4-security-xpath-nodetab-uaf.patch: See https://crbug.com/705445
diff --git a/third_party/libxml/chromium/libxml2-2.9.4-security-CVE-2017-7375-xmlParsePEReference-xxe.patch b/third_party/libxml/chromium/libxml2-2.9.4-security-CVE-2017-7375-xmlParsePEReference-xxe.patch
deleted file mode 100644
index 5548b88..0000000
--- a/third_party/libxml/chromium/libxml2-2.9.4-security-CVE-2017-7375-xmlParsePEReference-xxe.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-https://bugzilla.gnome.org/show_bug.cgi?id=780691
-
---- src/parser.c
-+++ src/parser.c
-@@ -8130,6 +8130,14 @@ xmlParsePEReference(xmlParserCtxtPtr ctxt)
- 	    if (xmlPushInput(ctxt, input) < 0)
- 		return;
- 	} else {
-+	    if ((entity->etype == XML_EXTERNAL_PARAMETER_ENTITY) &&
-+	        ((ctxt->options & XML_PARSE_NOENT) == 0) &&
-+	        ((ctxt->options & XML_PARSE_DTDVALID) == 0) &&
-+	        ((ctxt->options & XML_PARSE_DTDLOAD) == 0) &&
-+	        ((ctxt->options & XML_PARSE_DTDATTR) == 0) &&
-+	        (ctxt->replaceEntities == 0) &&
-+	        (ctxt->validate == 0))
-+	        return;
- 	    /*
- 	     * TODO !!!
- 	     * handle the extra spaces added before and after
diff --git a/third_party/libxml/chromium/roll.py b/third_party/libxml/chromium/roll.py
index f4da100..44cac5de 100755
--- a/third_party/libxml/chromium/roll.py
+++ b/third_party/libxml/chromium/roll.py
@@ -75,7 +75,6 @@
     'chromium-issue-620679.patch',
     'chromium-issue-628581.patch',
     'chromium-issue-683629.patch',
-    'libxml2-2.9.4-security-CVE-2017-7375-xmlParsePEReference-xxe.patch',
     'libxml2-2.9.4-security-CVE-2017-7376-nanohttp-out-of-bounds-write.patch',
     'libxml2-2.9.4-security-xpath-nodetab-uaf.patch',
     'libxml2-2.9.4-xmlDumpElementContent-null-deref.patch',
@@ -344,8 +343,9 @@
                          's/Version: .*$/Version: %s/' % commit)
 
             for patch in PATCHES:
-                subprocess.check_call('cat ../chromium/%s | patch -p1' % patch,
-                                      shell=True)
+                subprocess.check_call(
+                    'cat ../chromium/%s | patch -p1 --fuzz=0' % patch,
+                    shell=True)
 
             with WorkingDir('../linux'):
                 subprocess.check_call(
diff --git a/third_party/libxml/src/parser.c b/third_party/libxml/src/parser.c
index 8017df90b..e3f3fbd7 100644
--- a/third_party/libxml/src/parser.c
+++ b/third_party/libxml/src/parser.c
@@ -8120,14 +8120,6 @@
 			      "PEReference: %%%s; not found\n",
 			      name);
 	} else {
-	    if ((entity->etype == XML_EXTERNAL_PARAMETER_ENTITY) &&
-	        ((ctxt->options & XML_PARSE_NOENT) == 0) &&
-	        ((ctxt->options & XML_PARSE_DTDVALID) == 0) &&
-	        ((ctxt->options & XML_PARSE_DTDLOAD) == 0) &&
-	        ((ctxt->options & XML_PARSE_DTDATTR) == 0) &&
-	        (ctxt->replaceEntities == 0) &&
-	        (ctxt->validate == 0))
-	        return;
 	    /*
 	     * [ VC: Entity Declared ]
 	     * In a document with an external subset or external
diff --git a/third_party/sqlite/OWNERS b/third_party/sqlite/OWNERS
index fbe4ac8..b714e8a 100644
--- a/third_party/sqlite/OWNERS
+++ b/third_party/sqlite/OWNERS
@@ -1,5 +1,5 @@
 # Reviewers:
-shess@chromium.org
+pwnall@chromium.org
 
 # Owners:
 michaeln@chromium.org