diff --git a/DEPS b/DEPS
index cb0b98e..78524f0b 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': 'a07741a75aa694c0e7c00c2301c9de2daf9b5f9e',
+  'skia_revision': '9eac2ea77340d13bac58c7171e2e3971028ef8f8',
   # 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': 'eda659cc5e307f20ac1ad542ba12ab32eaf4c7ef',
+  'v8_revision': '2cba63960054ce9a88d4e3f1f5117b87566f6beb',
   # 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/WATCHLISTS b/WATCHLISTS
index 6712d66..9fa9e6ed 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1379,6 +1379,9 @@
                   '|third_party/WebKit/LayoutTests/fast/file' \
                   '|third_party/WebKit/public/.*File'
     },
+    'blink_frames': {
+      'filepath': 'third_party/WebKit/Source/core/frame/'
+    },
     'blink_quota': {
       'filepath': 'third_party/WebKit/Source/modules/quota/' \
                   '|third_party/WebKit/Source/web/.*Quota' \
@@ -1646,6 +1649,7 @@
     'blink_fileapi': ['kinuko+fileapi@chromium.org',
                       'nhiroki@chromium.org',
                       'tzik@chromium.org'],
+    'blink_frames': ['blink-reviews-frames@chromium.org'],
     'blink_geolocation': ['mlamouri+watch-blink@chromium.org',
                           'mvanouwerkerk+watch@chromium.org',
                           'timvolodine@chromium.org'],
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 66d31ad..96ddf2f 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -846,6 +846,8 @@
     "task_scheduler/scheduler_lock.h",
     "task_scheduler/scheduler_lock_impl.cc",
     "task_scheduler/scheduler_lock_impl.h",
+    "task_scheduler/scheduler_single_thread_task_runner_manager.cc",
+    "task_scheduler/scheduler_single_thread_task_runner_manager.h",
     "task_scheduler/scheduler_worker.cc",
     "task_scheduler/scheduler_worker.h",
     "task_scheduler/scheduler_worker_pool.h",
@@ -2090,6 +2092,7 @@
     "task_scheduler/delayed_task_manager_unittest.cc",
     "task_scheduler/priority_queue_unittest.cc",
     "task_scheduler/scheduler_lock_unittest.cc",
+    "task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc",
     "task_scheduler/scheduler_worker_pool_impl_unittest.cc",
     "task_scheduler/scheduler_worker_stack_unittest.cc",
     "task_scheduler/scheduler_worker_unittest.cc",
diff --git a/base/task_scheduler/scheduler_single_thread_task_runner_manager.cc b/base/task_scheduler/scheduler_single_thread_task_runner_manager.cc
new file mode 100644
index 0000000..71f9c0b
--- /dev/null
+++ b/base/task_scheduler/scheduler_single_thread_task_runner_manager.cc
@@ -0,0 +1,274 @@
+// 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 "base/task_scheduler/scheduler_single_thread_task_runner_manager.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/scheduler_worker.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+// Allows for checking the PlatformThread::CurrentRef() against a set
+// PlatformThreadRef atomically without using locks.
+class AtomicThreadRefChecker {
+ public:
+  AtomicThreadRefChecker() = default;
+  ~AtomicThreadRefChecker() = default;
+
+  void Set() {
+    thread_ref_ = PlatformThread::CurrentRef();
+    is_set_.Set();
+  }
+
+  bool IsCurrentThreadSameAsSetThread() {
+    return is_set_.IsSet() && thread_ref_ == PlatformThread::CurrentRef();
+  }
+
+ private:
+  AtomicFlag is_set_;
+  PlatformThreadRef thread_ref_;
+
+  DISALLOW_COPY_AND_ASSIGN(AtomicThreadRefChecker);
+};
+
+class SchedulerWorkerDelegate : public SchedulerWorker::Delegate {
+ public:
+  SchedulerWorkerDelegate(const std::string& thread_name)
+      : thread_name_(thread_name) {}
+
+  // SchedulerWorker::Delegate:
+  void OnMainEntry(SchedulerWorker* worker) override {
+    thread_ref_checker_.Set();
+    PlatformThread::SetName(thread_name_);
+  }
+
+  scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override {
+    AutoSchedulerLock auto_lock(sequence_lock_);
+    return std::move(sequence_);
+  }
+
+  void DidRunTask() override {}
+
+  void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override {
+    AutoSchedulerLock auto_lock(sequence_lock_);
+    DCHECK(!sequence_);
+    sequence_ = std::move(sequence);
+  }
+
+  TimeDelta GetSleepTimeout() override { return TimeDelta::Max(); }
+
+  bool CanDetach(SchedulerWorker* worker) override { return false; }
+
+  void OnDetach() override { NOTREACHED(); }
+
+  bool RunsTasksOnCurrentThread() {
+    // We check the thread ref instead of the sequence for the benefit of COM
+    // callbacks which may execute without a sequence context.
+    return thread_ref_checker_.IsCurrentThreadSameAsSetThread();
+  }
+
+ private:
+  const std::string thread_name_;
+
+  // Synchronizes access to |sequence_| and handles the fact that
+  // ReEnqueueSequence() is called on both the worker thread for reenqueuing
+  // the sequence and off of the worker thread to seed the sequence for
+  // GetWork().
+  SchedulerLock sequence_lock_;
+  scoped_refptr<Sequence> sequence_;
+
+  AtomicThreadRefChecker thread_ref_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(SchedulerWorkerDelegate);
+};
+
+}  // namespace
+
+class SchedulerSingleThreadTaskRunnerManager::SchedulerSingleThreadTaskRunner
+    : public SingleThreadTaskRunner {
+ public:
+  // Constructs a SchedulerSingleThreadTaskRunner that indirectly controls the
+  // lifetime of a dedicated |worker| for |traits|.
+  SchedulerSingleThreadTaskRunner(
+      SchedulerSingleThreadTaskRunnerManager* const outer,
+      const TaskTraits& traits,
+      SchedulerWorker* worker)
+      : outer_(outer), traits_(traits), worker_(worker) {
+    DCHECK(outer_);
+    DCHECK(worker_);
+  }
+
+  // SingleThreadTaskRunner:
+  bool PostDelayedTask(const tracked_objects::Location& from_here,
+                       const Closure& closure,
+                       TimeDelta delay) override;
+
+  bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+                                  const Closure& closure,
+                                  base::TimeDelta delay) override {
+    // Tasks are never nested within the task scheduler.
+    return PostDelayedTask(from_here, closure, delay);
+  }
+
+  bool RunsTasksOnCurrentThread() const override {
+    auto* delegate = static_cast<SchedulerWorkerDelegate*>(worker_->delegate());
+    return delegate->RunsTasksOnCurrentThread();
+  }
+
+ private:
+  ~SchedulerSingleThreadTaskRunner() override {
+    outer_->UnregisterSchedulerWorker(worker_);
+  }
+
+  void PostTaskNow(std::unique_ptr<Task> task);
+
+  // Sequence for all Tasks posted through this TaskRunner.
+  const scoped_refptr<Sequence> sequence_ = new Sequence;
+
+  SchedulerSingleThreadTaskRunnerManager* const outer_;
+  const TaskTraits traits_;
+  SchedulerWorker* const worker_;
+
+  DISALLOW_COPY_AND_ASSIGN(SchedulerSingleThreadTaskRunner);
+};
+
+bool SchedulerSingleThreadTaskRunnerManager::SchedulerSingleThreadTaskRunner::
+    PostDelayedTask(const tracked_objects::Location& from_here,
+                    const Closure& closure,
+                    TimeDelta delay) {
+  auto task = MakeUnique<Task>(from_here, closure, traits_, delay);
+  task->single_thread_task_runner_ref = this;
+
+  if (!outer_->task_tracker_->WillPostTask(task.get()))
+    return false;
+
+  if (task->delayed_run_time.is_null()) {
+    PostTaskNow(std::move(task));
+  } else {
+    outer_->delayed_task_manager_->AddDelayedTask(
+        std::move(task),
+        Bind(&SchedulerSingleThreadTaskRunner::PostTaskNow, Unretained(this)));
+  }
+  return true;
+}
+
+void SchedulerSingleThreadTaskRunnerManager::SchedulerSingleThreadTaskRunner::
+    PostTaskNow(std::unique_ptr<Task> task) {
+  const bool sequence_was_empty = sequence_->PushTask(std::move(task));
+  if (sequence_was_empty) {
+    auto* delegate = static_cast<SchedulerWorkerDelegate*>(worker_->delegate());
+    delegate->ReEnqueueSequence(sequence_);
+    worker_->WakeUp();
+  }
+}
+
+SchedulerSingleThreadTaskRunnerManager::SchedulerSingleThreadTaskRunnerManager(
+    const std::vector<SchedulerWorkerPoolParams>& worker_pool_params_vector,
+    const TaskScheduler::WorkerPoolIndexForTraitsCallback&
+        worker_pool_index_for_traits_callback,
+    TaskTracker* task_tracker,
+    DelayedTaskManager* delayed_task_manager)
+    : worker_pool_params_vector_(worker_pool_params_vector),
+      worker_pool_index_for_traits_callback_(
+          worker_pool_index_for_traits_callback),
+      task_tracker_(task_tracker),
+      delayed_task_manager_(delayed_task_manager) {
+  DCHECK_GT(worker_pool_params_vector_.size(), 0U);
+  DCHECK(worker_pool_index_for_traits_callback_);
+  DCHECK(task_tracker_);
+  DCHECK(delayed_task_manager_);
+}
+
+SchedulerSingleThreadTaskRunnerManager::
+    ~SchedulerSingleThreadTaskRunnerManager() {
+  DCHECK(workers_.empty()) << "SchedulerSingleThreadTaskRunners must outlive "
+                              "SchedulerSingleThreadTaskRunnerManager";
+}
+
+scoped_refptr<SingleThreadTaskRunner>
+SchedulerSingleThreadTaskRunnerManager::CreateSingleThreadTaskRunnerWithTraits(
+    const TaskTraits& traits) {
+  size_t index = worker_pool_index_for_traits_callback_.Run(traits);
+  DCHECK_LT(index, worker_pool_params_vector_.size());
+  return new SchedulerSingleThreadTaskRunner(
+      this, traits,
+      CreateAndRegisterSchedulerWorker(worker_pool_params_vector_[index]));
+}
+
+void SchedulerSingleThreadTaskRunnerManager::JoinForTesting() {
+  decltype(workers_) local_workers;
+  {
+    AutoSchedulerLock auto_lock(workers_lock_);
+    local_workers = std::move(workers_);
+  }
+
+  for (const auto& worker : local_workers)
+    worker->JoinForTesting();
+
+  {
+    AutoSchedulerLock auto_lock(workers_lock_);
+    DCHECK(workers_.empty())
+        << "New worker(s) unexpectedly registered during join.";
+    workers_ = std::move(local_workers);
+  }
+}
+
+SchedulerWorker*
+SchedulerSingleThreadTaskRunnerManager::CreateAndRegisterSchedulerWorker(
+    const SchedulerWorkerPoolParams& params) {
+  AutoSchedulerLock auto_lock(workers_lock_);
+  int id = next_worker_id_++;
+  auto delegate = MakeUnique<SchedulerWorkerDelegate>(base::StringPrintf(
+      "TaskSchedulerSingleThreadWorker%d%s", id, params.name().c_str()));
+  workers_.emplace_back(SchedulerWorker::Create(
+      params.priority_hint(), std::move(delegate), task_tracker_,
+      SchedulerWorker::InitialState::DETACHED));
+  return workers_.back().get();
+}
+
+void SchedulerSingleThreadTaskRunnerManager::UnregisterSchedulerWorker(
+    SchedulerWorker* worker) {
+  // Cleanup uses a SchedulerLock, so call Cleanup() after releasing
+  // |workers_lock_|.
+  scoped_refptr<SchedulerWorker> worker_to_destroy;
+  {
+    AutoSchedulerLock auto_lock(workers_lock_);
+
+    // We might be joining, so no-op this if |workers_| is empty.
+    if (workers_.empty())
+      return;
+
+    auto worker_iter =
+        std::find_if(workers_.begin(), workers_.end(),
+                     [worker](const scoped_refptr<SchedulerWorker>& candidate) {
+                       return candidate.get() == worker;
+                     });
+    DCHECK(worker_iter != workers_.end());
+    worker_to_destroy = std::move(*worker_iter);
+    workers_.erase(worker_iter);
+  }
+  worker_to_destroy->Cleanup();
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/task_scheduler/scheduler_single_thread_task_runner_manager.h b/base/task_scheduler/scheduler_single_thread_task_runner_manager.h
new file mode 100644
index 0000000..3b0ffd4f
--- /dev/null
+++ b/base/task_scheduler/scheduler_single_thread_task_runner_manager.h
@@ -0,0 +1,68 @@
+// 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 BASE_TASK_SCHEDULER_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_MANAGER_H_
+#define BASE_TASK_SCHEDULER_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_MANAGER_H_
+
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_scheduler/scheduler_lock.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/task_scheduler.h"
+
+namespace base {
+
+class TaskTraits;
+class SingleThreadTaskRunner;
+
+namespace internal {
+
+class DelayedTaskManager;
+class SchedulerWorker;
+class TaskTracker;
+
+class BASE_EXPORT SchedulerSingleThreadTaskRunnerManager final {
+ public:
+  SchedulerSingleThreadTaskRunnerManager(
+      const std::vector<SchedulerWorkerPoolParams>& worker_pool_params_vector,
+      const TaskScheduler::WorkerPoolIndexForTraitsCallback&
+          worker_pool_index_for_traits_callback,
+      TaskTracker* task_tracker,
+      DelayedTaskManager* delayed_task_manager);
+  ~SchedulerSingleThreadTaskRunnerManager();
+
+  scoped_refptr<SingleThreadTaskRunner> CreateSingleThreadTaskRunnerWithTraits(
+      const TaskTraits& traits);
+
+  void JoinForTesting();
+
+ private:
+  class SchedulerSingleThreadTaskRunner;
+
+  SchedulerWorker* CreateAndRegisterSchedulerWorker(
+      const SchedulerWorkerPoolParams& params);
+
+  void UnregisterSchedulerWorker(SchedulerWorker* worker);
+
+  const std::vector<SchedulerWorkerPoolParams> worker_pool_params_vector_;
+  const TaskScheduler::WorkerPoolIndexForTraitsCallback
+      worker_pool_index_for_traits_callback_;
+  TaskTracker* const task_tracker_;
+  DelayedTaskManager* const delayed_task_manager_;
+
+  // Synchronizes access to |workers_| and |worker_id_|.
+  SchedulerLock workers_lock_;
+  std::vector<scoped_refptr<SchedulerWorker>> workers_;
+  int next_worker_id_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(SchedulerSingleThreadTaskRunnerManager);
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_TASK_SCHEDULER_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_MANAGER_H_
diff --git a/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc b/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
new file mode 100644
index 0000000..6db240a7
--- /dev/null
+++ b/base/task_scheduler/scheduler_single_thread_task_runner_manager_unittest.cc
@@ -0,0 +1,254 @@
+// 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 "base/task_scheduler/scheduler_single_thread_task_runner_manager.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+enum WorkerPoolType {
+  BACKGROUND_WORKER_POOL = 0,
+  FOREGROUND_WORKER_POOL,
+};
+
+static size_t GetThreadPoolIndexForTraits(const TaskTraits& traits) {
+  return traits.priority() == TaskPriority::BACKGROUND ? BACKGROUND_WORKER_POOL
+                                                       : FOREGROUND_WORKER_POOL;
+}
+
+class TaskSchedulerSingleThreadTaskRunnerManagerTest : public testing::Test {
+ public:
+  TaskSchedulerSingleThreadTaskRunnerManagerTest()
+      : service_thread_("TaskSchedulerServiceThread") {}
+
+  void SetUp() override {
+    service_thread_.Start();
+
+    using StandbyThreadPolicy = SchedulerWorkerPoolParams::StandbyThreadPolicy;
+
+    std::vector<SchedulerWorkerPoolParams> params_vector;
+
+    ASSERT_EQ(BACKGROUND_WORKER_POOL, params_vector.size());
+    params_vector.emplace_back("Background", ThreadPriority::BACKGROUND,
+                               StandbyThreadPolicy::LAZY, 1U, TimeDelta::Max());
+
+    ASSERT_EQ(FOREGROUND_WORKER_POOL, params_vector.size());
+    params_vector.emplace_back("Foreground", ThreadPriority::NORMAL,
+                               StandbyThreadPolicy::LAZY, 1U, TimeDelta::Max());
+
+    delayed_task_manager_ =
+        MakeUnique<DelayedTaskManager>(service_thread_.task_runner());
+    single_thread_task_runner_manager_ =
+        MakeUnique<SchedulerSingleThreadTaskRunnerManager>(
+            params_vector, Bind(&GetThreadPoolIndexForTraits), &task_tracker_,
+            delayed_task_manager_.get());
+  }
+
+  void TearDown() override {
+    single_thread_task_runner_manager_->JoinForTesting();
+    single_thread_task_runner_manager_.reset();
+    delayed_task_manager_.reset();
+    service_thread_.Stop();
+  }
+
+ protected:
+  std::unique_ptr<SchedulerSingleThreadTaskRunnerManager>
+      single_thread_task_runner_manager_;
+  TaskTracker task_tracker_;
+
+ private:
+  Thread service_thread_;
+  std::unique_ptr<DelayedTaskManager> delayed_task_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSingleThreadTaskRunnerManagerTest);
+};
+
+void CaptureThreadRef(PlatformThreadRef* thread_ref) {
+  ASSERT_TRUE(thread_ref);
+  *thread_ref = PlatformThread::CurrentRef();
+}
+
+void CaptureThreadPriority(ThreadPriority* thread_priority) {
+  ASSERT_TRUE(thread_priority);
+  *thread_priority = PlatformThread::GetCurrentThreadPriority();
+}
+
+void ShouldNotRun() {
+  ADD_FAILURE() << "Ran a task that shouldn't run.";
+}
+
+}  // namespace
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, DifferentThreadsUsed) {
+  scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithShutdownBehavior(
+                  TaskShutdownBehavior::BLOCK_SHUTDOWN));
+  scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithShutdownBehavior(
+                  TaskShutdownBehavior::BLOCK_SHUTDOWN));
+
+  PlatformThreadRef thread_ref_1;
+  task_runner_1->PostTask(FROM_HERE, Bind(&CaptureThreadRef, &thread_ref_1));
+  PlatformThreadRef thread_ref_2;
+  task_runner_2->PostTask(FROM_HERE, Bind(&CaptureThreadRef, &thread_ref_2));
+
+  task_tracker_.Shutdown();
+
+  ASSERT_FALSE(thread_ref_1.is_null());
+  ASSERT_FALSE(thread_ref_2.is_null());
+  EXPECT_NE(thread_ref_1, thread_ref_2);
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, PrioritySetCorrectly) {
+  // Why are events used here instead of the task tracker?
+  // Shutting down can cause priorities to get raised. This means we have to use
+  // events to determine when a task is run.
+  scoped_refptr<SingleThreadTaskRunner> task_runner_background =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithPriority(TaskPriority::BACKGROUND));
+  scoped_refptr<SingleThreadTaskRunner> task_runner_user_visible =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithPriority(TaskPriority::USER_VISIBLE));
+  scoped_refptr<SingleThreadTaskRunner> task_runner_user_blocking =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits()
+                  .WithPriority(TaskPriority::USER_BLOCKING)
+                  .WithShutdownBehavior(TaskShutdownBehavior::BLOCK_SHUTDOWN));
+
+  ThreadPriority thread_priority_background;
+  task_runner_background->PostTask(
+      FROM_HERE, Bind(&CaptureThreadPriority, &thread_priority_background));
+  WaitableEvent waitable_event_background(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  task_runner_background->PostTask(
+      FROM_HERE,
+      Bind(&WaitableEvent::Signal, Unretained(&waitable_event_background)));
+
+  ThreadPriority thread_priority_user_visible;
+  task_runner_user_visible->PostTask(
+      FROM_HERE, Bind(&CaptureThreadPriority, &thread_priority_user_visible));
+  WaitableEvent waitable_event_user_visible(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  task_runner_user_visible->PostTask(
+      FROM_HERE,
+      Bind(&WaitableEvent::Signal, Unretained(&waitable_event_user_visible)));
+
+  ThreadPriority thread_priority_user_blocking;
+  task_runner_user_blocking->PostTask(
+      FROM_HERE, Bind(&CaptureThreadPriority, &thread_priority_user_blocking));
+  WaitableEvent waitable_event_user_blocking(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  task_runner_user_blocking->PostTask(
+      FROM_HERE,
+      Bind(&WaitableEvent::Signal, Unretained(&waitable_event_user_blocking)));
+
+  waitable_event_background.Wait();
+  waitable_event_user_visible.Wait();
+  waitable_event_user_blocking.Wait();
+
+  if (Lock::HandlesMultipleThreadPriorities())
+    EXPECT_EQ(ThreadPriority::BACKGROUND, thread_priority_background);
+  else
+    EXPECT_EQ(ThreadPriority::NORMAL, thread_priority_background);
+  EXPECT_EQ(ThreadPriority::NORMAL, thread_priority_user_visible);
+  EXPECT_EQ(ThreadPriority::NORMAL, thread_priority_user_blocking);
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, PostTaskAfterShutdown) {
+  auto task_runner = single_thread_task_runner_manager_
+                         ->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
+  task_tracker_.Shutdown();
+  EXPECT_FALSE(task_runner->PostTask(FROM_HERE, Bind(&ShouldNotRun)));
+}
+
+// Verify that a Task runs shortly after its delay expires.
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest, PostDelayedTask) {
+  TimeTicks start_time = TimeTicks::Now();
+
+  // Post a task with a short delay.
+  WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+                         WaitableEvent::InitialState::NOT_SIGNALED);
+  auto task_runner = single_thread_task_runner_manager_
+                         ->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
+  EXPECT_TRUE(task_runner->PostDelayedTask(
+      FROM_HERE, Bind(&WaitableEvent::Signal, Unretained(&task_ran)),
+      TestTimeouts::tiny_timeout()));
+
+  // Wait until the task runs.
+  task_ran.Wait();
+
+  // Expect the task to run after its delay expires, but not more than 250 ms
+  // after that.
+  const TimeDelta actual_delay = TimeTicks::Now() - start_time;
+  EXPECT_GE(actual_delay, TestTimeouts::tiny_timeout());
+  EXPECT_LT(actual_delay,
+            TimeDelta::FromMilliseconds(250) + TestTimeouts::tiny_timeout());
+}
+
+TEST_F(TaskSchedulerSingleThreadTaskRunnerManagerTest,
+       RunsTasksOnCurrentThread) {
+  scoped_refptr<SingleThreadTaskRunner> task_runner_1 =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithShutdownBehavior(
+                  TaskShutdownBehavior::BLOCK_SHUTDOWN));
+  scoped_refptr<SingleThreadTaskRunner> task_runner_2 =
+      single_thread_task_runner_manager_
+          ->CreateSingleThreadTaskRunnerWithTraits(
+              TaskTraits().WithShutdownBehavior(
+                  TaskShutdownBehavior::BLOCK_SHUTDOWN));
+
+  EXPECT_FALSE(task_runner_1->RunsTasksOnCurrentThread());
+  EXPECT_FALSE(task_runner_2->RunsTasksOnCurrentThread());
+
+  task_runner_1->PostTask(
+      FROM_HERE,
+      Bind(
+          [](scoped_refptr<SingleThreadTaskRunner> task_runner_1,
+             scoped_refptr<SingleThreadTaskRunner> task_runner_2) {
+            EXPECT_TRUE(task_runner_1->RunsTasksOnCurrentThread());
+            EXPECT_FALSE(task_runner_2->RunsTasksOnCurrentThread());
+          },
+          task_runner_1, task_runner_2));
+
+  task_runner_2->PostTask(
+      FROM_HERE,
+      Bind(
+          [](scoped_refptr<SingleThreadTaskRunner> task_runner_1,
+             scoped_refptr<SingleThreadTaskRunner> task_runner_2) {
+            EXPECT_FALSE(task_runner_1->RunsTasksOnCurrentThread());
+            EXPECT_TRUE(task_runner_2->RunsTasksOnCurrentThread());
+          },
+          task_runner_1, task_runner_2));
+
+  task_tracker_.Shutdown();
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/task_scheduler/scheduler_worker_pool_impl.cc b/base/task_scheduler/scheduler_worker_pool_impl.cc
index 22aa2c65..9a6a20b 100644
--- a/base/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/base/task_scheduler/scheduler_worker_pool_impl.cc
@@ -141,6 +141,7 @@
 
 }  // namespace
 
+// TODO(http://crbug.com/694823): Remove this and supporting framework.
 // A task runner that runs tasks with the SINGLE_THREADED ExecutionMode.
 class SchedulerWorkerPoolImpl::SchedulerSingleThreadTaskRunner :
     public SingleThreadTaskRunner {
diff --git a/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc
index dfaa9c7f..d8aa212 100644
--- a/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc
+++ b/base/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -719,6 +719,104 @@
   worker_pool_->DisallowWorkerDetachmentForTesting();
 }
 
+TEST_F(TaskSchedulerWorkerPoolHistogramTest, NumTasksBeforeDetach) {
+  InitializeWorkerPool(kReclaimTimeForDetachTests, kNumWorkersInWorkerPool);
+
+  auto histogrammed_thread_task_runner =
+      worker_pool_->CreateSequencedTaskRunnerWithTraits(
+          TaskTraits().WithBaseSyncPrimitives());
+
+  // Post 3 tasks and hold the thread for idle thread stack ordering.
+  // This test assumes |histogrammed_thread_task_runner| gets assigned the same
+  // thread for each of its tasks.
+  PlatformThreadRef thread_ref;
+  histogrammed_thread_task_runner->PostTask(
+      FROM_HERE, Bind(
+                     [](PlatformThreadRef* thread_ref) {
+                       ASSERT_TRUE(thread_ref);
+                       *thread_ref = PlatformThread::CurrentRef();
+                     },
+                     Unretained(&thread_ref)));
+  histogrammed_thread_task_runner->PostTask(
+      FROM_HERE, Bind(
+                     [](PlatformThreadRef thread_ref) {
+                       EXPECT_EQ(thread_ref, PlatformThreadRef());
+                     },
+                     thread_ref));
+  WaitableEvent detach_thread_running(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  WaitableEvent detach_thread_continue(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  histogrammed_thread_task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](PlatformThreadRef thread_ref, WaitableEvent* detach_thread_running,
+             WaitableEvent* detach_thread_continue) {
+            EXPECT_EQ(thread_ref, PlatformThreadRef());
+            detach_thread_running->Signal();
+            detach_thread_continue->Wait();
+          },
+          thread_ref, Unretained(&detach_thread_running),
+          Unretained(&detach_thread_continue)));
+
+  detach_thread_running.Wait();
+
+  // To allow the SchedulerWorker associated with
+  // |histogrammed_thread_task_runner| to detach, make sure it isn't on top of
+  // the idle stack by waking up another SchedulerWorker via
+  // |task_runner_for_top_idle|. |histogrammed_thread_task_runner| should
+  // release and go idle first and then |task_runner_for_top_idle| should
+  // release and go idle. This allows the SchedulerWorker associated with
+  // |histogrammed_thread_task_runner| to detach.
+  WaitableEvent top_idle_thread_running(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  WaitableEvent top_idle_thread_continue(
+      WaitableEvent::ResetPolicy::MANUAL,
+      WaitableEvent::InitialState::NOT_SIGNALED);
+  auto task_runner_for_top_idle =
+      worker_pool_->CreateSequencedTaskRunnerWithTraits(
+          TaskTraits().WithBaseSyncPrimitives());
+  task_runner_for_top_idle->PostTask(
+      FROM_HERE, Bind(
+                     [](PlatformThreadRef thread_ref,
+                        WaitableEvent* top_idle_thread_running,
+                        WaitableEvent* top_idle_thread_continue) {
+                       EXPECT_NE(thread_ref, PlatformThread::CurrentRef())
+                           << "Worker reused. Thread will not detach and the "
+                              "histogram value will be wrong.";
+                       top_idle_thread_running->Signal();
+                       top_idle_thread_continue->Wait();
+                     },
+                     thread_ref, Unretained(&top_idle_thread_running),
+                     Unretained(&top_idle_thread_continue)));
+  top_idle_thread_running.Wait();
+  detach_thread_continue.Signal();
+  // Wait for the thread processing the |histogrammed_thread_task_runner| work
+  // to go to the idle stack.
+  PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+  top_idle_thread_continue.Signal();
+  // Allow the thread processing the |histogrammed_thread_task_runner| work to
+  // detach.
+  PlatformThread::Sleep(kReclaimTimeForDetachTests +
+                        kReclaimTimeForDetachTests);
+  worker_pool_->WaitForAllWorkersIdleForTesting();
+  worker_pool_->DisallowWorkerDetachmentForTesting();
+
+  // Verify that counts were recorded to the histogram as expected.
+  const auto* histogram = worker_pool_->num_tasks_before_detach_histogram();
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(1));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(2));
+  EXPECT_EQ(1, histogram->SnapshotSamples()->GetCount(3));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(4));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(5));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(6));
+  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
+}
+
 namespace {
 
 void NotReachedReEnqueueSequenceCallback(scoped_refptr<Sequence> sequence) {
@@ -726,53 +824,8 @@
       << "Unexpected invocation of NotReachedReEnqueueSequenceCallback.";
 }
 
-void CaptureThreadId(PlatformThreadId* thread_id) {
-  ASSERT_TRUE(thread_id);
-  *thread_id = PlatformThread::CurrentId();
-}
-
-void VerifyThreadIdIsNot(PlatformThreadId thread_id) {
-  EXPECT_NE(thread_id, PlatformThread::CurrentId());
-}
-
 }  // namespace
 
-TEST_F(TaskSchedulerWorkerPoolHistogramTest, NumTasksBeforeDetach) {
-  InitializeWorkerPool(kReclaimTimeForDetachTests, kNumWorkersInWorkerPool);
-
-  // This test assumes that the TaskRunners aren't assigned to the same worker.
-  auto task_runner =
-      worker_pool_->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
-  auto other_task_runner =
-      worker_pool_->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
-
-  // Post 3 tasks and wait until they run.
-  PlatformThreadId thread_id;
-  task_runner->PostTask(FROM_HERE,
-                        Bind(&CaptureThreadId, Unretained(&thread_id)));
-  task_runner->PostTask(FROM_HERE, Bind(&DoNothing));
-  task_runner->PostTask(FROM_HERE, Bind(&DoNothing));
-  worker_pool_->WaitForAllWorkersIdleForTesting();
-
-  // To allow the SchedulerWorker associated with |task_runner| to detach:
-  // - Make sure it isn't on top of the idle stack by waking up another
-  //   SchedulerWorker and waiting until it goes back to sleep.
-  // - Release |task_runner|.
-  other_task_runner->PostTask(FROM_HERE, Bind(&VerifyThreadIdIsNot, thread_id));
-  worker_pool_->WaitForAllWorkersIdleForTesting();
-  task_runner = nullptr;
-
-  // Allow the SchedulerWorker that was associated with |task_runner| to detach.
-  PlatformThread::Sleep(kReclaimTimeForDetachTests + kExtraTimeToWaitForDetach);
-  worker_pool_->DisallowWorkerDetachmentForTesting();
-
-  // Verify that counts were recorded to the histogram as expected.
-  const auto* histogram = worker_pool_->num_tasks_before_detach_histogram();
-  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(0));
-  EXPECT_EQ(1, histogram->SnapshotSamples()->GetCount(3));
-  EXPECT_EQ(0, histogram->SnapshotSamples()->GetCount(10));
-}
-
 TEST(TaskSchedulerWorkerPoolStandbyPolicyTest, InitLazy) {
   TaskTracker task_tracker;
   DelayedTaskManager delayed_task_manager(
diff --git a/base/task_scheduler/scheduler_worker_pool_params.cc b/base/task_scheduler/scheduler_worker_pool_params.cc
index 0747c2e..43a66d0 100644
--- a/base/task_scheduler/scheduler_worker_pool_params.cc
+++ b/base/task_scheduler/scheduler_worker_pool_params.cc
@@ -21,9 +21,9 @@
       backward_compatibility_(backward_compatibility) {}
 
 SchedulerWorkerPoolParams::SchedulerWorkerPoolParams(
-    SchedulerWorkerPoolParams&& other) = default;
+    const SchedulerWorkerPoolParams& other) = default;
 
 SchedulerWorkerPoolParams& SchedulerWorkerPoolParams::operator=(
-    SchedulerWorkerPoolParams&& other) = default;
+    const SchedulerWorkerPoolParams& other) = default;
 
 }  // namespace base
diff --git a/base/task_scheduler/scheduler_worker_pool_params.h b/base/task_scheduler/scheduler_worker_pool_params.h
index 5f90fd4..4c0548c5 100644
--- a/base/task_scheduler/scheduler_worker_pool_params.h
+++ b/base/task_scheduler/scheduler_worker_pool_params.h
@@ -7,7 +7,6 @@
 
 #include <string>
 
-#include "base/macros.h"
 #include "base/task_scheduler/scheduler_worker_params.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
@@ -42,8 +41,8 @@
       TimeDelta suggested_reclaim_time,
       SchedulerBackwardCompatibility backward_compatibility =
           SchedulerBackwardCompatibility::DISABLED);
-  SchedulerWorkerPoolParams(SchedulerWorkerPoolParams&& other);
-  SchedulerWorkerPoolParams& operator=(SchedulerWorkerPoolParams&& other);
+  SchedulerWorkerPoolParams(const SchedulerWorkerPoolParams& other);
+  SchedulerWorkerPoolParams& operator=(const SchedulerWorkerPoolParams& other);
 
   const std::string& name() const { return name_; }
   ThreadPriority priority_hint() const { return priority_hint_; }
@@ -63,8 +62,6 @@
   size_t max_threads_;
   TimeDelta suggested_reclaim_time_;
   SchedulerBackwardCompatibility backward_compatibility_;
-
-  DISALLOW_COPY_AND_ASSIGN(SchedulerWorkerPoolParams);
 };
 
 }  // namespace base
diff --git a/base/task_scheduler/task_scheduler_impl_unittest.cc b/base/task_scheduler/task_scheduler_impl_unittest.cc
index 6aaf4af..bacedb7 100644
--- a/base/task_scheduler/task_scheduler_impl_unittest.cc
+++ b/base/task_scheduler/task_scheduler_impl_unittest.cc
@@ -322,5 +322,49 @@
           TaskTraits().WithPriority(TaskPriority::USER_BLOCKING).MayBlock()));
 }
 
+// Verify that the RunsTasksOnCurrentThread() method of a SequencedTaskRunner
+// returns false when called from a task that isn't part of the sequence.
+TEST_F(TaskSchedulerImplTest, SequencedRunsTasksOnCurrentThread) {
+  auto single_thread_task_runner =
+      scheduler_->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
+  auto sequenced_task_runner =
+      scheduler_->CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+  WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+                         WaitableEvent::InitialState::NOT_SIGNALED);
+  single_thread_task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](scoped_refptr<TaskRunner> sequenced_task_runner,
+             WaitableEvent* task_ran) {
+            EXPECT_FALSE(sequenced_task_runner->RunsTasksOnCurrentThread());
+            task_ran->Signal();
+          },
+          sequenced_task_runner, Unretained(&task_ran)));
+  task_ran.Wait();
+}
+
+// Verify that the RunsTasksOnCurrentThread() method of a SingleThreadTaskRunner
+// returns false when called from a task that isn't part of the sequence.
+TEST_F(TaskSchedulerImplTest, SingleThreadRunsTasksOnCurrentThread) {
+  auto sequenced_task_runner =
+      scheduler_->CreateSequencedTaskRunnerWithTraits(TaskTraits());
+  auto single_thread_task_runner =
+      scheduler_->CreateSingleThreadTaskRunnerWithTraits(TaskTraits());
+
+  WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+                         WaitableEvent::InitialState::NOT_SIGNALED);
+  sequenced_task_runner->PostTask(
+      FROM_HERE,
+      Bind(
+          [](scoped_refptr<TaskRunner> single_thread_task_runner,
+             WaitableEvent* task_ran) {
+            EXPECT_FALSE(single_thread_task_runner->RunsTasksOnCurrentThread());
+            task_ran->Signal();
+          },
+          single_thread_task_runner, Unretained(&task_ran)));
+  task_ran.Wait();
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index f886904..8c0d8e4 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -63,6 +63,8 @@
     return id_ == other.id_;
   }
 
+  bool operator!=(PlatformThreadRef other) const { return id_ != other.id_; }
+
   bool is_null() const {
     return id_ == 0;
   }
diff --git a/chrome/VERSION b/chrome/VERSION
index 03c5666..8300881 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=58
 MINOR=0
-BUILD=3025
+BUILD=3026
 PATCH=0
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 9db262e..8e15742 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -324,8 +324,9 @@
       extensions::SOURCE_NETWORK);
 
   file_manager::VolumeManager::Get(browser()->profile())
-      ->AddVolumeForTesting(file_manager::Volume::CreateForProvidedFileSystem(
-          info, file_manager::MOUNT_CONTEXT_AUTO));
+      ->AddVolumeForTesting(
+          make_linked_ptr(file_manager::Volume::CreateForProvidedFileSystem(
+              info, file_manager::MOUNT_CONTEXT_AUTO)));
 
   // We will call fileManagerPrivate.unmountVolume once. To test that method, we
   // check that UnmountPath is really called with the same value.
diff --git a/chrome/browser/chromeos/extensions/file_manager/job_event_router.cc b/chrome/browser/chromeos/extensions/file_manager/job_event_router.cc
index 529bc17..daf2fb3 100644
--- a/chrome/browser/chromeos/extensions/file_manager/job_event_router.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/job_event_router.cc
@@ -48,7 +48,7 @@
 
   // Add new job info.
   UpdateBytes(job_info);
-  drive_jobs_[job_info.job_id] = base::MakeUnique<drive::JobInfo>(job_info);
+  drive_jobs_[job_info.job_id] = make_linked_ptr(new drive::JobInfo(job_info));
 
   ScheduleDriveFileTransferEvent(
       job_info, file_manager_private::TRANSFER_STATE_IN_PROGRESS,
diff --git a/chrome/browser/chromeos/extensions/file_manager/job_event_router.h b/chrome/browser/chromeos/extensions/file_manager/job_event_router.h
index ea787a67..85e4dc5 100644
--- a/chrome/browser/chromeos/extensions/file_manager/job_event_router.h
+++ b/chrome/browser/chromeos/extensions/file_manager/job_event_router.h
@@ -13,6 +13,7 @@
 #include <string>
 
 #include "base/macros.h"
+#include "base/memory/linked_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
@@ -81,7 +82,7 @@
   base::TimeDelta event_delay_;
 
   // Set of job that are in the job schedular.
-  std::map<drive::JobID, std::unique_ptr<drive::JobInfo>> drive_jobs_;
+  std::map<drive::JobID, linked_ptr<drive::JobInfo>> drive_jobs_;
 
   // Job info of pending event. |ScheduleDriveFileTransferEvent| registers
   // timeout callback to dispatch this.
diff --git a/chrome/browser/chromeos/extensions/file_manager/job_event_router_unittest.cc b/chrome/browser/chromeos/extensions/file_manager/job_event_router_unittest.cc
index 13f220e..8b84ea50 100644
--- a/chrome/browser/chromeos/extensions/file_manager/job_event_router_unittest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/job_event_router_unittest.cc
@@ -8,7 +8,6 @@
 #include <stdint.h>
 
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -21,7 +20,7 @@
   JobEventRouterImpl() : JobEventRouter(base::TimeDelta::FromMilliseconds(0)) {
     listener_extension_ids_.insert("extension_a");
   }
-  std::vector<std::unique_ptr<base::DictionaryValue>> events;
+  std::vector<linked_ptr<base::DictionaryValue>> events;
 
   void SetListenerExtensionIds(std::set<std::string> extension_ids) {
     listener_extension_ids_ = extension_ids;
@@ -50,7 +49,7 @@
       std::unique_ptr<base::ListValue> event_args) override {
     const base::DictionaryValue* event;
     event_args->GetDictionary(0, &event);
-    events.push_back(base::WrapUnique(event->DeepCopy()));
+    events.push_back(make_linked_ptr(event->DeepCopy()));
   }
 
  private:
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc
index b8aef48..8d592b6 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -168,10 +168,10 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForDrive(Profile* profile) {
+Volume* Volume::CreateForDrive(Profile* profile) {
   const base::FilePath& drive_path =
       drive::util::GetDriveMountPointPath(profile);
-  std::unique_ptr<Volume> volume(new Volume());
+  Volume* const volume = new Volume;
   volume->type_ = VOLUME_TYPE_GOOGLE_DRIVE;
   volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
   volume->source_path_ = drive_path;
@@ -184,9 +184,8 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForDownloads(
-    const base::FilePath& downloads_path) {
-  std::unique_ptr<Volume> volume(new Volume());
+Volume* Volume::CreateForDownloads(const base::FilePath& downloads_path) {
+  Volume* const volume = new Volume;
   volume->type_ = VOLUME_TYPE_DOWNLOADS_DIRECTORY;
   volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
   // Keep source_path empty.
@@ -199,10 +198,10 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForRemovable(
+Volume* Volume::CreateForRemovable(
     const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
     const chromeos::disks::DiskMountManager::Disk* disk) {
-  std::unique_ptr<Volume> volume(new Volume());
+  Volume* const volume = new Volume;
   volume->type_ = MountTypeToVolumeType(mount_point.mount_type);
   volume->source_path_ = base::FilePath(mount_point.source_path);
   volume->source_ = mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE
@@ -229,11 +228,11 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForProvidedFileSystem(
+Volume* Volume::CreateForProvidedFileSystem(
     const chromeos::file_system_provider::ProvidedFileSystemInfo&
         file_system_info,
     MountContext mount_context) {
-  std::unique_ptr<Volume> volume(new Volume());
+  Volume* const volume = new Volume;
   volume->file_system_id_ = file_system_info.file_system_id();
   volume->extension_id_ = file_system_info.extension_id();
   switch (file_system_info.source()) {
@@ -261,10 +260,10 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForMTP(const base::FilePath& mount_path,
-                                             const std::string& label,
-                                             bool read_only) {
-  std::unique_ptr<Volume> volume(new Volume());
+Volume* Volume::CreateForMTP(const base::FilePath& mount_path,
+                             const std::string& label,
+                             bool read_only) {
+  Volume* const volume = new Volume;
   volume->type_ = VOLUME_TYPE_MTP;
   volume->mount_path_ = mount_path;
   volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
@@ -279,9 +278,8 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForMediaView(
-    const std::string& root_document_id) {
-  std::unique_ptr<Volume> volume(new Volume());
+Volume* Volume::CreateForMediaView(const std::string& root_document_id) {
+  Volume* const volume = new Volume;
   volume->type_ = VOLUME_TYPE_MEDIA_VIEW;
   volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
   volume->source_ = SOURCE_SYSTEM;
@@ -296,12 +294,11 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForTesting(
-    const base::FilePath& path,
-    VolumeType volume_type,
-    chromeos::DeviceType device_type,
-    bool read_only) {
-  std::unique_ptr<Volume> volume(new Volume());
+Volume* Volume::CreateForTesting(const base::FilePath& path,
+                                 VolumeType volume_type,
+                                 chromeos::DeviceType device_type,
+                                 bool read_only) {
+  Volume* const volume = new Volume;
   volume->type_ = volume_type;
   volume->device_type_ = device_type;
   // Keep source_path empty.
@@ -314,10 +311,9 @@
 }
 
 // static
-std::unique_ptr<Volume> Volume::CreateForTesting(
-    const base::FilePath& device_path,
-    const base::FilePath& mount_path) {
-  std::unique_ptr<Volume> volume(new Volume());
+Volume* Volume::CreateForTesting(const base::FilePath& device_path,
+                                 const base::FilePath& mount_path) {
+  Volume* const volume = new Volume;
   volume->system_path_prefix_ = device_path;
   volume->mount_path_ = mount_path;
   return volume;
@@ -359,14 +355,14 @@
   DCHECK(success);
 
   DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-               Volume::CreateForDownloads(downloads));
+               make_linked_ptr(Volume::CreateForDownloads(downloads)));
 
   // Subscribe to DriveIntegrationService.
   if (drive_integration_service_) {
     drive_integration_service_->AddObserver(this);
     if (drive_integration_service_->IsMounted()) {
       DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-                   Volume::CreateForDrive(profile_));
+                   make_linked_ptr(Volume::CreateForDrive(profile_)));
     }
   }
 
@@ -386,9 +382,9 @@
     std::vector<ProvidedFileSystemInfo> file_system_info_list =
         file_system_provider_service_->GetProvidedFileSystemInfoList();
     for (size_t i = 0; i < file_system_info_list.size(); ++i) {
-      std::unique_ptr<Volume> volume = Volume::CreateForProvidedFileSystem(
-          file_system_info_list[i], MOUNT_CONTEXT_AUTO);
-      DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
+      linked_ptr<Volume> volume(Volume::CreateForProvidedFileSystem(
+          file_system_info_list[i], MOUNT_CONTEXT_AUTO));
+      DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume);
     }
   }
 
@@ -461,7 +457,6 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   std::vector<base::WeakPtr<Volume>> result;
-  result.reserve(mounted_volumes_.size());
   for (const auto& pair : mounted_volumes_) {
     result.push_back(pair.second->AsWeakPtr());
   }
@@ -485,13 +480,13 @@
   base::FilePath old_path;
   if (FindDownloadsMountPointPath(profile_, &old_path)) {
     DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
-                   *Volume::CreateForDownloads(old_path));
+                   make_linked_ptr(Volume::CreateForDownloads(old_path)));
   }
 
   bool success = RegisterDownloadsMountPoint(profile_, path);
   DoMountEvent(
       success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
-      Volume::CreateForDownloads(path));
+      make_linked_ptr(Volume::CreateForDownloads(path)));
   return success;
 }
 
@@ -500,14 +495,14 @@
                                         chromeos::DeviceType device_type,
                                         bool read_only) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DoMountEvent(
-      chromeos::MOUNT_ERROR_NONE,
-      Volume::CreateForTesting(path, volume_type, device_type, read_only));
+  DoMountEvent(chromeos::MOUNT_ERROR_NONE,
+               make_linked_ptr(Volume::CreateForTesting(
+                   path, volume_type, device_type, read_only)));
 }
 
-void VolumeManager::AddVolumeForTesting(std::unique_ptr<Volume> volume) {
+void VolumeManager::AddVolumeForTesting(const linked_ptr<Volume>& volume) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
+  DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume);
 }
 
 void VolumeManager::OnFileSystemMounted() {
@@ -516,13 +511,15 @@
   // Raise mount event.
   // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed
   // or network is unreachable. These two errors will be handled later.
-  DoMountEvent(chromeos::MOUNT_ERROR_NONE, Volume::CreateForDrive(profile_));
+  linked_ptr<Volume> volume(Volume::CreateForDrive(profile_));
+  DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume);
 }
 
 void VolumeManager::OnFileSystemBeingUnmounted() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, *Volume::CreateForDrive(profile_));
+  linked_ptr<Volume> volume(Volume::CreateForDrive(profile_));
+  DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, volume);
 }
 
 void VolumeManager::OnDiskEvent(
@@ -632,14 +629,14 @@
   // Notify a mounting/unmounting event to observers.
   const chromeos::disks::DiskMountManager::Disk* const disk =
       disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path);
-  std::unique_ptr<Volume> volume = Volume::CreateForRemovable(mount_info, disk);
+  linked_ptr<Volume> volume(Volume::CreateForRemovable(mount_info, disk));
   switch (event) {
     case chromeos::disks::DiskMountManager::MOUNTING: {
-      DoMountEvent(error_code, std::move(volume));
+      DoMountEvent(error_code, volume);
       return;
     }
     case chromeos::disks::DiskMountManager::UNMOUNTING:
-      DoUnmountEvent(error_code, *volume);
+      DoUnmountEvent(error_code, volume);
       return;
   }
   NOTREACHED();
@@ -697,8 +694,8 @@
       break;
   }
 
-  std::unique_ptr<Volume> volume =
-      Volume::CreateForProvidedFileSystem(file_system_info, volume_context);
+  linked_ptr<Volume> volume(
+      Volume::CreateForProvidedFileSystem(file_system_info, volume_context));
 
   // TODO(mtomasz): Introduce own type, and avoid using MountError internally,
   // since it is related to cros disks only.
@@ -715,7 +712,7 @@
       break;
   }
 
-  DoMountEvent(mount_error, std::move(volume));
+  DoMountEvent(mount_error, volume);
 }
 
 void VolumeManager::OnProvidedFileSystemUnmount(
@@ -727,9 +724,9 @@
   const chromeos::MountError mount_error = error == base::File::FILE_OK
                                                ? chromeos::MOUNT_ERROR_NONE
                                                : chromeos::MOUNT_ERROR_UNKNOWN;
-  std::unique_ptr<Volume> volume = Volume::CreateForProvidedFileSystem(
-      file_system_info, MOUNT_CONTEXT_UNKNOWN);
-  DoUnmountEvent(mount_error, *volume);
+  linked_ptr<Volume> volume(Volume::CreateForProvidedFileSystem(
+      file_system_info, MOUNT_CONTEXT_UNKNOWN));
+  DoUnmountEvent(mount_error, volume);
 }
 
 void VolumeManager::OnExternalStorageDisabledChangedUnmountCallback(
@@ -756,18 +753,24 @@
 
   if (enabled) {
     DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-                 Volume::CreateForMediaView(arc::kImagesRootDocumentId));
+                 linked_ptr<Volume>(
+                     Volume::CreateForMediaView(arc::kImagesRootDocumentId)));
     DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-                 Volume::CreateForMediaView(arc::kVideosRootDocumentId));
+                 linked_ptr<Volume>(
+                     Volume::CreateForMediaView(arc::kVideosRootDocumentId)));
     DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-                 Volume::CreateForMediaView(arc::kAudioRootDocumentId));
+                 linked_ptr<Volume>(
+                     Volume::CreateForMediaView(arc::kAudioRootDocumentId)));
   } else {
     DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
-                   *Volume::CreateForMediaView(arc::kImagesRootDocumentId));
+                   linked_ptr<Volume>(
+                       Volume::CreateForMediaView(arc::kImagesRootDocumentId)));
     DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
-                   *Volume::CreateForMediaView(arc::kVideosRootDocumentId));
+                   linked_ptr<Volume>(
+                       Volume::CreateForMediaView(arc::kVideosRootDocumentId)));
     DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
-                   *Volume::CreateForMediaView(arc::kAudioRootDocumentId));
+                   linked_ptr<Volume>(
+                       Volume::CreateForMediaView(arc::kAudioRootDocumentId)));
   }
 
   arc_volumes_mounted_ = enabled;
@@ -855,8 +858,8 @@
                  base::Unretained(MTPDeviceMapService::GetInstance()),
                  info.location(), fsid, read_only));
 
-  std::unique_ptr<Volume> volume = Volume::CreateForMTP(path, label, read_only);
-  DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
+  linked_ptr<Volume> volume(Volume::CreateForMTP(path, label, read_only));
+  DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume);
 }
 
 void VolumeManager::OnRemovableStorageDetached(
@@ -864,9 +867,9 @@
   if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id()))
     return;
 
-  for (const auto& mounted_volume : mounted_volumes_) {
+  for (const auto mounted_volume : mounted_volumes_) {
     if (mounted_volume.second->source_path().value() == info.location()) {
-      DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, *mounted_volume.second.get());
+      DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, mounted_volume.second);
 
       const std::string fsid = GetMountPointNameForMediaStorage(info);
       storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(fsid);
@@ -886,7 +889,7 @@
     return;
   }
 
-  std::vector<std::unique_ptr<Volume>> archives;
+  std::vector<linked_ptr<Volume>> archives;
 
   const chromeos::disks::DiskMountManager::MountPointMap& mount_points =
       disk_mount_manager_->mount_points();
@@ -896,14 +899,14 @@
        ++it) {
     if (it->second.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
       // Archives are mounted after other types of volume. See below.
-      archives.push_back(Volume::CreateForRemovable(it->second, nullptr));
+      archives.push_back(
+          make_linked_ptr(Volume::CreateForRemovable(it->second, NULL)));
       continue;
     }
-    DoMountEvent(
-        chromeos::MOUNT_ERROR_NONE,
-        Volume::CreateForRemovable(
-            it->second,
-            disk_mount_manager_->FindDiskBySourcePath(it->second.source_path)));
+    DoMountEvent(chromeos::MOUNT_ERROR_NONE,
+                 make_linked_ptr(Volume::CreateForRemovable(
+                     it->second, disk_mount_manager_->FindDiskBySourcePath(
+                                     it->second.source_path))));
   }
 
   // We mount archives only if they are opened from currently mounted volumes.
@@ -913,26 +916,24 @@
     if (done[i])
       continue;
 
-    std::vector<std::unique_ptr<Volume>> chain;
-    // done[x] = true means archives[x] is null and that volume is in |chain|.
+    std::vector<linked_ptr<Volume>> chain;
     done[i] = true;
-    chain.push_back(std::move(archives[i]));
+    chain.push_back(archives[i]);
 
     // If archives[i]'s source_path is in another archive, mount it first.
     for (size_t parent = i + 1; parent < archives.size(); ++parent) {
       if (!done[parent] &&
           archives[parent]->mount_path().IsParent(
               chain.back()->source_path())) {
-        // done[parent] started false, so archives[parent] is non-null.
         done[parent] = true;
-        chain.push_back(std::move(archives[parent]));
+        chain.push_back(archives[parent]);
         parent = i + 1;  // Search archives[parent]'s parent from the beginning.
       }
     }
 
     // Mount from the tail of chain.
     for (size_t i = chain.size(); i > 0; --i) {
-      DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(chain[i - 1]));
+      DoMountEvent(chromeos::MOUNT_ERROR_NONE, chain[i - 1]);
     }
   }
 }
@@ -946,7 +947,7 @@
 }
 
 void VolumeManager::DoMountEvent(chromeos::MountError error_code,
-                                 std::unique_ptr<Volume> volume) {
+                                 const linked_ptr<Volume>& volume) {
   // Archive files are mounted globally in system. We however don't want to show
   // archives from profile-specific folders (Drive/Downloads) of other users in
   // multi-profile session. To this end, we filter out archives not on the
@@ -971,27 +972,25 @@
     return;
   }
 
-  Volume* raw_volume = volume.get();
   if (error_code == chromeos::MOUNT_ERROR_NONE || volume->mount_condition()) {
-    mounted_volumes_[volume->volume_id()] = std::move(volume);
-    UMA_HISTOGRAM_ENUMERATION("FileBrowser.VolumeType", raw_volume->type(),
+    mounted_volumes_[volume->volume_id()] = volume;
+    UMA_HISTOGRAM_ENUMERATION("FileBrowser.VolumeType", volume->type(),
                               NUM_VOLUME_TYPE);
   }
 
   for (auto& observer : observers_)
-    observer.OnVolumeMounted(error_code, *raw_volume);
+    observer.OnVolumeMounted(error_code, *volume);
 }
 
 void VolumeManager::DoUnmountEvent(chromeos::MountError error_code,
-                                   const Volume& volume) {
-  auto iter = mounted_volumes_.find(volume.volume_id());
-  if (iter == mounted_volumes_.end())
+                                   const linked_ptr<Volume>& volume) {
+  if (mounted_volumes_.find(volume->volume_id()) == mounted_volumes_.end())
     return;
   if (error_code == chromeos::MOUNT_ERROR_NONE)
-    mounted_volumes_.erase(iter);
+    mounted_volumes_.erase(volume->volume_id());
 
   for (auto& observer : observers_)
-    observer.OnVolumeUnmounted(error_code, volume);
+    observer.OnVolumeUnmounted(error_code, *volume.get());
 }
 
 }  // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.h b/chrome/browser/chromeos/file_manager/volume_manager.h
index 1658d149..46014daf 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.h
+++ b/chrome/browser/chromeos/file_manager/volume_manager.h
@@ -14,6 +14,7 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/memory/linked_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "chrome/browser/chromeos/arc/arc_session_manager.h"
@@ -79,29 +80,25 @@
   ~Volume();
 
   // Factory static methods for different volume types.
-  static std::unique_ptr<Volume> CreateForDrive(Profile* profile);
-  static std::unique_ptr<Volume> CreateForDownloads(
-      const base::FilePath& downloads_path);
-  static std::unique_ptr<Volume> CreateForRemovable(
+  static Volume* CreateForDrive(Profile* profile);
+  static Volume* CreateForDownloads(const base::FilePath& downloads_path);
+  static Volume* CreateForRemovable(
       const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
       const chromeos::disks::DiskMountManager::Disk* disk);
-  static std::unique_ptr<Volume> CreateForProvidedFileSystem(
+  static Volume* CreateForProvidedFileSystem(
       const chromeos::file_system_provider::ProvidedFileSystemInfo&
           file_system_info,
       MountContext mount_context);
-  static std::unique_ptr<Volume> CreateForMTP(const base::FilePath& mount_path,
-                                              const std::string& label,
-                                              bool read_only);
-  static std::unique_ptr<Volume> CreateForMediaView(
-      const std::string& root_document_id);
-  static std::unique_ptr<Volume> CreateForTesting(
-      const base::FilePath& path,
-      VolumeType volume_type,
-      chromeos::DeviceType device_type,
-      bool read_only);
-  static std::unique_ptr<Volume> CreateForTesting(
-      const base::FilePath& device_path,
-      const base::FilePath& mount_path);
+  static Volume* CreateForMTP(const base::FilePath& mount_path,
+                              const std::string& label,
+                              bool read_only);
+  static Volume* CreateForMediaView(const std::string& root_document_id);
+  static Volume* CreateForTesting(const base::FilePath& path,
+                                  VolumeType volume_type,
+                                  chromeos::DeviceType device_type,
+                                  bool read_only);
+  static Volume* CreateForTesting(const base::FilePath& device_path,
+                                  const base::FilePath& mount_path);
 
   // Getters for all members. See below for details.
   const std::string& volume_id() const { return volume_id_; }
@@ -273,7 +270,7 @@
                            bool read_only);
 
   // For testing purpose, adds the volume info to the volume manager.
-  void AddVolumeForTesting(std::unique_ptr<Volume> volume);
+  void AddVolumeForTesting(const linked_ptr<Volume>& volume);
 
   // drive::DriveIntegrationServiceObserver overrides.
   void OnFileSystemMounted() override;
@@ -325,8 +322,9 @@
   void OnDiskMountManagerRefreshed(bool success);
   void OnStorageMonitorInitialized();
   void DoMountEvent(chromeos::MountError error_code,
-                    std::unique_ptr<Volume> volume);
-  void DoUnmountEvent(chromeos::MountError error_code, const Volume& volume);
+                    const linked_ptr<Volume>& volume);
+  void DoUnmountEvent(chromeos::MountError error_code,
+                      const linked_ptr<Volume>& volume);
   void OnExternalStorageDisabledChangedUnmountCallback(
       chromeos::MountError error_code);
 
@@ -338,7 +336,7 @@
   chromeos::file_system_provider::Service*
       file_system_provider_service_;  // Not owned by this class.
   GetMtpStorageInfoCallback get_mtp_storage_info_callback_;
-  std::map<std::string, std::unique_ptr<Volume>> mounted_volumes_;
+  std::map<std::string, linked_ptr<Volume>> mounted_volumes_;
   std::unique_ptr<SnapshotManager> snapshot_manager_;
   bool arc_volumes_mounted_ = false;
 
diff --git a/chrome/test/data/webui/md_history/md_history_browsertest.js b/chrome/test/data/webui/md_history/md_history_browsertest.js
index 754d587..22d988a 100644
--- a/chrome/test/data/webui/md_history/md_history_browsertest.js
+++ b/chrome/test/data/webui/md_history/md_history_browsertest.js
@@ -107,7 +107,16 @@
   ]),
 };
 
-TEST_F('MaterialHistoryListTest', 'All', function() {
+// Times out on debug builders and may time out on memory bots because
+// the History page can take several seconds to load in a Debug build. See
+// https://crbug.com/669227.
+GEN('#if defined(MEMORY_SANITIZER) || !defined(NDEBUG)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
+
+TEST_F('MaterialHistoryListTest', 'MAYBE_All', function() {
   mocha.run();
 });
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index b2d2315..5b5af15 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1037,6 +1037,8 @@
     "renderer_host/input/mouse_wheel_event_queue.h",
     "renderer_host/input/mouse_wheel_rails_filter_mac.cc",
     "renderer_host/input/mouse_wheel_rails_filter_mac.h",
+    "renderer_host/input/passthrough_touch_event_queue.cc",
+    "renderer_host/input/passthrough_touch_event_queue.h",
     "renderer_host/input/render_widget_host_latency_tracker.cc",
     "renderer_host/input/render_widget_host_latency_tracker.h",
     "renderer_host/input/stylus_text_selector.cc",
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 86ed1c67..2de801a 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -17,6 +17,7 @@
 #include "content/browser/renderer_host/input/input_ack_handler.h"
 #include "content/browser/renderer_host/input/input_router_client.h"
 #include "content/browser/renderer_host/input/legacy_touch_event_queue.h"
+#include "content/browser/renderer_host/input/passthrough_touch_event_queue.h"
 #include "content/browser/renderer_host/input/touch_event_queue.h"
 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
 #include "content/common/content_constants_internal.h"
@@ -91,9 +92,15 @@
       wheel_event_queue_(this,
                          base::FeatureList::IsEnabled(
                              features::kTouchpadAndWheelScrollLatching)),
-      touch_event_queue_(new LegacyTouchEventQueue(this, config.touch_config)),
       gesture_event_queue_(this, this, config.gesture_config),
       device_scale_factor_(1.f) {
+  if (base::FeatureList::IsEnabled(features::kRafAlignedTouchInputEvents))
+    touch_event_queue_.reset(
+        new PassthroughTouchEventQueue(this, config.touch_config));
+  else
+    touch_event_queue_.reset(
+        new LegacyTouchEventQueue(this, config.touch_config));
+
   DCHECK(sender);
   DCHECK(client);
   DCHECK(ack_handler);
diff --git a/content/browser/renderer_host/input/legacy_touch_event_queue.h b/content/browser/renderer_host/input/legacy_touch_event_queue.h
index 8468cde1..742301a 100644
--- a/content/browser/renderer_host/input/legacy_touch_event_queue.h
+++ b/content/browser/renderer_host/input/legacy_touch_event_queue.h
@@ -96,7 +96,7 @@
   }
 
  private:
-  friend class TouchEventQueueTest;
+  friend class LegacyTouchEventQueueTest;
 
   bool HasPendingAsyncTouchMoveForTesting() const;
   bool IsTimeoutRunningForTesting() const;
diff --git a/content/browser/renderer_host/input/touch_event_queue_unittest.cc b/content/browser/renderer_host/input/legacy_touch_event_queue_unittest.cc
similarity index 95%
rename from content/browser/renderer_host/input/touch_event_queue_unittest.cc
rename to content/browser/renderer_host/input/legacy_touch_event_queue_unittest.cc
index b20f6921..7f391d2b 100644
--- a/content/browser/renderer_host/input/touch_event_queue_unittest.cc
+++ b/content/browser/renderer_host/input/legacy_touch_event_queue_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/renderer_host/input/touch_event_queue.h"
+#include "content/browser/renderer_host/input/legacy_touch_event_queue.h"
 
 #include <stddef.h>
 
@@ -15,7 +15,6 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "content/browser/renderer_host/input/legacy_touch_event_queue.h"
 #include "content/browser/renderer_host/input/timeout_monitor.h"
 #include "content/common/input/synthetic_web_input_event_builders.h"
 #include "content/common/input/web_touch_event_traits.h"
@@ -40,15 +39,15 @@
 }
 }  // namespace
 
-class TouchEventQueueTest : public testing::Test,
-                            public TouchEventQueueClient {
+class LegacyTouchEventQueueTest : public testing::Test,
+                                  public TouchEventQueueClient {
  public:
-  TouchEventQueueTest()
+  LegacyTouchEventQueueTest()
       : acked_event_count_(0),
         last_acked_event_state_(INPUT_EVENT_ACK_STATE_UNKNOWN),
         slop_length_dips_(0) {}
 
-  ~TouchEventQueueTest() override {}
+  ~LegacyTouchEventQueueTest() override {}
 
   // testing::Test
   void SetUp() override {
@@ -82,9 +81,8 @@
     if (followup_gesture_event_) {
       std::unique_ptr<WebGestureEvent> followup_gesture_event =
           std::move(followup_gesture_event_);
-      queue_->OnGestureScrollEvent(
-          GestureEventWithLatencyInfo(*followup_gesture_event,
-                                      ui::LatencyInfo()));
+      queue_->OnGestureScrollEvent(GestureEventWithLatencyInfo(
+          *followup_gesture_event, ui::LatencyInfo()));
     }
   }
 
@@ -247,9 +245,7 @@
     touch_event_.setTimeStampSeconds(touch_event_.timeStampSeconds() + seconds);
   }
 
-  void ResetTouchEvent() {
-    touch_event_ = SyntheticWebTouchEvent();
-  }
+  void ResetTouchEvent() { touch_event_ = SyntheticWebTouchEvent(); }
 
   size_t GetAndResetAckedEventCount() {
     size_t count = acked_event_count_;
@@ -283,17 +279,13 @@
     return queue_->HasPendingAsyncTouchMoveForTesting();
   }
 
-  size_t queued_event_count() const {
-    return queue_->size();
-  }
+  size_t queued_event_count() const { return queue_->size(); }
 
   const WebTouchEvent& latest_event() const {
     return queue_->GetLatestEventForTesting().event;
   }
 
-  const WebTouchEvent& acked_event() const {
-    return last_acked_event_;
-  }
+  const WebTouchEvent& acked_event() const { return last_acked_event_; }
 
   const WebTouchEvent& sent_event() const {
     DCHECK(!sent_events_.empty());
@@ -346,9 +338,8 @@
   std::deque<int> sent_events_ids_;
 };
 
-
 // Tests that touch-events are queued properly.
-TEST_F(TouchEventQueueTest, Basic) {
+TEST_F(LegacyTouchEventQueueTest, Basic) {
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -376,7 +367,7 @@
 }
 
 // Tests that touch-events with multiple points are queued properly.
-TEST_F(TouchEventQueueTest, BasicMultiTouch) {
+TEST_F(LegacyTouchEventQueueTest, BasicMultiTouch) {
   const size_t kPointerCount = 10;
   for (float i = 0; i < kPointerCount; ++i)
     PressTouchPoint(i, i);
@@ -422,7 +413,8 @@
 
 // Tests that the touch-queue continues delivering events for an active touch
 // sequence after all handlers are removed.
-TEST_F(TouchEventQueueTest, TouchesForwardedIfHandlerRemovedDuringSequence) {
+TEST_F(LegacyTouchEventQueueTest,
+       TouchesForwardedIfHandlerRemovedDuringSequence) {
   OnHasTouchEventHandlers(true);
   EXPECT_EQ(0U, queued_event_count());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
@@ -485,7 +477,7 @@
 
 // Tests that addition of a touch handler during a touch sequence will not cause
 // the remaining sequence to be forwarded.
-TEST_F(TouchEventQueueTest, ActiveSequenceNotForwardedWhenHandlersAdded) {
+TEST_F(LegacyTouchEventQueueTest, ActiveSequenceNotForwardedWhenHandlersAdded) {
   OnHasTouchEventHandlers(false);
 
   // Send a touch-press event while there is no handler.
@@ -512,7 +504,7 @@
 // Tests that removal of a touch handler during a touch sequence will prevent
 // the remaining sequence from being forwarded, even if another touch handler is
 // registered during the same touch sequence.
-TEST_F(TouchEventQueueTest, ActiveSequenceDroppedWhenHandlersRemoved) {
+TEST_F(LegacyTouchEventQueueTest, ActiveSequenceDroppedWhenHandlersRemoved) {
   // Send a touch-press event.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -566,7 +558,7 @@
 
 // Tests that removal/addition of a touch handler without any intervening
 // touch activity has no affect on touch forwarding.
-TEST_F(TouchEventQueueTest,
+TEST_F(LegacyTouchEventQueueTest,
        ActiveSequenceUnaffectedByRepeatedHandlerRemovalAndAddition) {
   // Send a touch-press event.
   PressTouchPoint(1, 1);
@@ -593,7 +585,7 @@
 }
 
 // Tests that touch-events are coalesced properly in the queue.
-TEST_F(TouchEventQueueTest, Coalesce) {
+TEST_F(LegacyTouchEventQueueTest, Coalesce) {
   // Send a touch-press event.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -633,7 +625,7 @@
 
 // Tests that an event that has already been sent but hasn't been ack'ed yet
 // doesn't get coalesced with newer events.
-TEST_F(TouchEventQueueTest, SentTouchEventDoesNotCoalesce) {
+TEST_F(LegacyTouchEventQueueTest, SentTouchEventDoesNotCoalesce) {
   // Send a touch-press event.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -660,7 +652,7 @@
 }
 
 // Tests that coalescing works correctly for multi-touch events.
-TEST_F(TouchEventQueueTest, MultiTouch) {
+TEST_F(LegacyTouchEventQueueTest, MultiTouch) {
   // Press the first finger.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -694,7 +686,7 @@
 }
 
 // Tests that the touch-event queue is robust to redundant acks.
-TEST_F(TouchEventQueueTest, SpuriousAcksIgnored) {
+TEST_F(LegacyTouchEventQueueTest, SpuriousAcksIgnored) {
   // Trigger a spurious ack.
   SendTouchEventAckWithID(INPUT_EVENT_ACK_STATE_CONSUMED, 0);
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
@@ -717,7 +709,7 @@
 // main thread in the renderer). Also tests that all queued/coalesced touch
 // events are flushed immediately when the ACK for the touch-press comes back
 // with NO_CONSUMER status.
-TEST_F(TouchEventQueueTest, NoConsumer) {
+TEST_F(LegacyTouchEventQueueTest, NoConsumer) {
   // The first touch-press should reach the renderer.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -781,7 +773,7 @@
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
-TEST_F(TouchEventQueueTest, ConsumerIgnoreMultiFinger) {
+TEST_F(LegacyTouchEventQueueTest, ConsumerIgnoreMultiFinger) {
   // Interleave three pointer press, move and release events.
   PressTouchPoint(1, 1);
   MoveTouchPoint(0, 5, 5);
@@ -835,7 +827,7 @@
 }
 
 // Tests that touch-event's enqueued via a touch ack are properly handled.
-TEST_F(TouchEventQueueTest, AckWithFollowupEvents) {
+TEST_F(LegacyTouchEventQueueTest, AckWithFollowupEvents) {
   // Queue a touch down.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, queued_event_count());
@@ -874,7 +866,7 @@
 }
 
 // Tests that touch-events can be synchronously ack'ed.
-TEST_F(TouchEventQueueTest, SynchronousAcks) {
+TEST_F(LegacyTouchEventQueueTest, SynchronousAcks) {
   // TouchStart
   SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
   PressTouchPoint(1, 1);
@@ -912,7 +904,7 @@
 
 // Tests that followup events triggered by an immediate ack from
 // TouchEventQueue::QueueEvent() are properly handled.
-TEST_F(TouchEventQueueTest, ImmediateAckWithFollowupEvents) {
+TEST_F(LegacyTouchEventQueueTest, ImmediateAckWithFollowupEvents) {
   // Create a touch event that will be queued synchronously by a touch ack.
   WebTouchEvent followup_event(
       WebInputEvent::TouchStart, WebInputEvent::NoModifiers,
@@ -942,7 +934,7 @@
 }
 
 // Tests basic TouchEvent forwarding suppression.
-TEST_F(TouchEventQueueTest, NoTouchBasic) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchBasic) {
   // Disable TouchEvent forwarding.
   OnHasTouchEventHandlers(false);
   PressTouchPoint(30, 5);
@@ -979,8 +971,7 @@
 }
 
 // Tests that IsTouchStartPendingAck works correctly.
-TEST_F(TouchEventQueueTest, PendingStart) {
-
+TEST_F(LegacyTouchEventQueueTest, PendingStart) {
   EXPECT_FALSE(IsPendingAckTouchStart());
 
   // Send the touchstart for one point (#1).
@@ -1025,7 +1016,7 @@
 }
 
 // Tests that the touch timeout is started when sending certain touch types.
-TEST_F(TouchEventQueueTest, TouchTimeoutTypes) {
+TEST_F(LegacyTouchEventQueueTest, TouchTimeoutTypes) {
   SetUpForTimeoutTesting();
 
   // Sending a TouchStart will start the timeout.
@@ -1059,7 +1050,7 @@
 // Tests that a delayed TouchEvent ack will trigger a TouchCancel timeout,
 // disabling touch forwarding until the next TouchStart is received after
 // the timeout events are ack'ed.
-TEST_F(TouchEventQueueTest, TouchTimeoutBasic) {
+TEST_F(LegacyTouchEventQueueTest, TouchTimeoutBasic) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1113,7 +1104,7 @@
 
 // Tests that the timeout is never started if the renderer consumes
 // a TouchEvent from the current touch sequence.
-TEST_F(TouchEventQueueTest, NoTouchTimeoutIfRendererIsConsumingGesture) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchTimeoutIfRendererIsConsumingGesture) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1147,7 +1138,7 @@
 
 // Tests that the timeout is never started if the renderer consumes
 // a TouchEvent from the current touch sequence.
-TEST_F(TouchEventQueueTest, NoTouchTimeoutIfDisabledAfterTouchStart) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchTimeoutIfDisabledAfterTouchStart) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1173,7 +1164,7 @@
 }
 
 // Tests that the timeout is never started if the ack is synchronous.
-TEST_F(TouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1185,7 +1176,7 @@
 
 // Tests that the timeout does not fire if explicitly disabled while an event
 // is in-flight.
-TEST_F(TouchEventQueueTest, NoTouchTimeoutIfDisabledWhileTimerIsActive) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchTimeoutIfDisabledWhileTimerIsActive) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1200,7 +1191,7 @@
 }
 
 // Tests that the timeout does not fire if the delay is zero.
-TEST_F(TouchEventQueueTest, NoTouchTimeoutIfTimeoutDelayIsZero) {
+TEST_F(LegacyTouchEventQueueTest, NoTouchTimeoutIfTimeoutDelayIsZero) {
   SetUpForTimeoutTesting(base::TimeDelta(), base::TimeDelta());
 
   // As the delay is zero, timeout behavior should be disabled.
@@ -1211,7 +1202,7 @@
 }
 
 // Tests that timeout delays for mobile sites take effect when appropriate.
-TEST_F(TouchEventQueueTest, TouchTimeoutConfiguredForMobile) {
+TEST_F(LegacyTouchEventQueueTest, TouchTimeoutConfiguredForMobile) {
   base::TimeDelta desktop_delay = DefaultTouchTimeoutDelay();
   base::TimeDelta mobile_delay = base::TimeDelta();
   SetUpForTimeoutTesting(desktop_delay, mobile_delay);
@@ -1238,7 +1229,7 @@
 
 // Tests that a TouchCancel timeout plays nice when the timed out touch stream
 // turns into a scroll gesture sequence.
-TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
+TEST_F(LegacyTouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1294,7 +1285,8 @@
 // Tests that a TouchCancel timeout plays nice when the timed out touch stream
 // turns into a scroll gesture sequence, but the original event acks are
 // significantly delayed.
-TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGestureAndDelayedAck) {
+TEST_F(LegacyTouchEventQueueTest,
+       TouchTimeoutWithFollowupGestureAndDelayedAck) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1351,7 +1343,7 @@
 
 // Tests that a delayed TouchEvent ack will not trigger a TouchCancel timeout if
 // the timed-out event had no consumer.
-TEST_F(TouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
+TEST_F(LegacyTouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
   SetUpForTimeoutTesting();
 
   // Queue a TouchStart.
@@ -1390,7 +1382,7 @@
 
 // Tests that TouchMove's movedBeyondSlopRegion is set to false if within the
 // boundary-inclusive slop region for an unconsumed TouchStart.
-TEST_F(TouchEventQueueTest, TouchMovedBeyondSlopRegionCheck) {
+TEST_F(LegacyTouchEventQueueTest, TouchMovedBeyondSlopRegionCheck) {
   SetUpForTouchMoveSlopTesting(kSlopLengthDips);
 
   // Queue a TouchStart.
@@ -1440,7 +1432,7 @@
   const float kFortyFiveDegreeSlopLengthXY =
       kSlopLengthDips * std::sqrt(2.f) / 2;
   MoveTouchPoint(0, kFortyFiveDegreeSlopLengthXY + .2f,
-                    kFortyFiveDegreeSlopLengthXY + .2f);
+                 kFortyFiveDegreeSlopLengthXY + .2f);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
@@ -1451,7 +1443,8 @@
 
 // Tests that even very small TouchMove's movedBeyondSlopRegion is set to true
 // when the slop region's dimension is 0.
-TEST_F(TouchEventQueueTest, MovedBeyondSlopRegionAlwaysTrueIfDimensionZero) {
+TEST_F(LegacyTouchEventQueueTest,
+       MovedBeyondSlopRegionAlwaysTrueIfDimensionZero) {
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -1470,7 +1463,8 @@
 
 // Tests that secondary touch points can be forwarded even if the primary touch
 // point had no consumer.
-TEST_F(TouchEventQueueTest, SecondaryTouchForwardedAfterPrimaryHadNoConsumer) {
+TEST_F(LegacyTouchEventQueueTest,
+       SecondaryTouchForwardedAfterPrimaryHadNoConsumer) {
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
@@ -1499,7 +1493,7 @@
 
 // Tests that secondary touch points can be forwarded after scrolling begins
 // while first touch point has no consumer.
-TEST_F(TouchEventQueueTest, NoForwardingAfterScrollWithNoTouchConsumers) {
+TEST_F(LegacyTouchEventQueueTest, NoForwardingAfterScrollWithNoTouchConsumers) {
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
@@ -1529,7 +1523,7 @@
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
-TEST_F(TouchEventQueueTest, AsyncTouch) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouch) {
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -1537,29 +1531,29 @@
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   for (int i = 0; i < 3; ++i) {
-   SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
-                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                        INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
 
-   MoveTouchPoint(0, 10, 5+i);
-   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
-   EXPECT_FALSE(HasPendingAsyncTouchMove());
-   EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
-   EXPECT_EQ(0U, queued_event_count());
-   EXPECT_EQ(1U, GetAndResetSentEventCount());
+    MoveTouchPoint(0, 10, 5 + i);
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+    EXPECT_FALSE(HasPendingAsyncTouchMove());
+    EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+    EXPECT_EQ(0U, queued_event_count());
+    EXPECT_EQ(1U, GetAndResetSentEventCount());
 
-   // Consuming a scroll event will throttle subsequent touchmoves.
-   SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
-                       INPUT_EVENT_ACK_STATE_CONSUMED);
-   MoveTouchPoint(0, 10, 7+i);
-   EXPECT_TRUE(HasPendingAsyncTouchMove());
-   EXPECT_EQ(0U, queued_event_count());
-   EXPECT_EQ(0U, GetAndResetSentEventCount());
+    // Consuming a scroll event will throttle subsequent touchmoves.
+    SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                        INPUT_EVENT_ACK_STATE_CONSUMED);
+    MoveTouchPoint(0, 10, 7 + i);
+    EXPECT_TRUE(HasPendingAsyncTouchMove());
+    EXPECT_EQ(0U, queued_event_count());
+    EXPECT_EQ(0U, GetAndResetSentEventCount());
   }
 }
 
 // Ensure that touchmove's are appropriately throttled during a typical
 // scroll sequences that transitions between scrolls consumed and unconsumed.
-TEST_F(TouchEventQueueTest, AsyncTouchThrottledAfterScroll) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchThrottledAfterScroll) {
   // Process a TouchStart
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -1746,7 +1740,7 @@
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
-TEST_F(TouchEventQueueTest, AsyncTouchFlushedByTouchEnd) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchFlushedByTouchEnd) {
   PressTouchPoint(0, 0);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -1802,7 +1796,7 @@
 
 // Ensure that async touch dispatch and touch ack timeout interactions work
 // appropriately.
-TEST_F(TouchEventQueueTest, AsyncTouchWithAckTimeout) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchWithAckTimeout) {
   SetUpForTimeoutTesting();
 
   // The touchstart should start the timeout.
@@ -1884,7 +1878,7 @@
 
 // Ensure that if the touch ack for an async touchmove triggers a follow-up
 // touch event, that follow-up touch will be forwarded appropriately.
-TEST_F(TouchEventQueueTest, AsyncTouchWithTouchCancelAfterAck) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchWithTouchCancelAfterAck) {
   PressTouchPoint(0, 0);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -1935,7 +1929,7 @@
 
 // Ensure that the async touch is fully reset if the touch sequence restarts
 // without properly terminating.
-TEST_F(TouchEventQueueTest, AsyncTouchWithHardTouchStartReset) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchWithHardTouchStartReset) {
   PressTouchPoint(0, 0);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -1976,7 +1970,7 @@
 // Ensure that even when the interval expires, we still need to wait for the
 // ack sent back from render to send the next async touchmove once the scroll
 // starts.
-TEST_F(TouchEventQueueTest, SendNextThrottledAsyncTouchMoveAfterAck) {
+TEST_F(LegacyTouchEventQueueTest, SendNextThrottledAsyncTouchMoveAfterAck) {
   // Process a TouchStart
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -2038,7 +2032,7 @@
 // Ensure that even when we receive the ack from render, we still need to wait
 // for the interval expires to send the next async touchmove once the scroll
 // starts.
-TEST_F(TouchEventQueueTest, SendNextAsyncTouchMoveAfterAckAndTimeExpire) {
+TEST_F(LegacyTouchEventQueueTest, SendNextAsyncTouchMoveAfterAckAndTimeExpire) {
   // Process a TouchStart
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -2097,7 +2091,7 @@
   EXPECT_EQ(1U, uncancelable_touch_moves_pending_ack_count());
 }
 
-TEST_F(TouchEventQueueTest, AsyncTouchFlushedByNonTouchMove) {
+TEST_F(LegacyTouchEventQueueTest, AsyncTouchFlushedByNonTouchMove) {
   // Process a TouchStart
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -2212,7 +2206,7 @@
 // Ensure that even when we receive the ack from render, we still need to wait
 // for the interval expires to send the next async touchmove once the scroll
 // starts.
-TEST_F(TouchEventQueueTest, DoNotIncreaseIfClientConsumeAsyncTouchMove) {
+TEST_F(LegacyTouchEventQueueTest, DoNotIncreaseIfClientConsumeAsyncTouchMove) {
   // Process a TouchStart
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -2274,7 +2268,7 @@
   EXPECT_EQ(0U, uncancelable_touch_moves_pending_ack_count());
 }
 
-TEST_F(TouchEventQueueTest, TouchAbsorptionWithConsumedFirstMove) {
+TEST_F(LegacyTouchEventQueueTest, TouchAbsorptionWithConsumedFirstMove) {
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -2313,7 +2307,7 @@
   EXPECT_EQ(0U, GetAndResetSentEventCount());
 }
 
-TEST_F(TouchEventQueueTest, TouchStartCancelableDuringScroll) {
+TEST_F(LegacyTouchEventQueueTest, TouchStartCancelableDuringScroll) {
   // Queue a touchstart and touchmove that go unconsumed, transitioning to an
   // active scroll sequence.
   PressTouchPoint(0, 1);
@@ -2372,7 +2366,7 @@
   ASSERT_EQ(1U, GetAndResetSentEventCount());
 }
 
-TEST_F(TouchEventQueueTest, UnseenTouchPointerIdsNotForwarded) {
+TEST_F(LegacyTouchEventQueueTest, UnseenTouchPointerIdsNotForwarded) {
   SyntheticWebTouchEvent event;
   event.PressPoint(0, 0);
   SendTouchEvent(event);
@@ -2411,7 +2405,7 @@
 }
 
 // Tests that touch points states are correct in TouchMove events.
-TEST_F(TouchEventQueueTest, PointerStatesInTouchMove) {
+TEST_F(LegacyTouchEventQueueTest, PointerStatesInTouchMove) {
   PressTouchPoint(1, 1);
   PressTouchPoint(2, 2);
   PressTouchPoint(3, 3);
@@ -2468,7 +2462,7 @@
 
 // Tests that touch point state is correct in TouchMove events
 // when point properties other than position changed.
-TEST_F(TouchEventQueueTest, PointerStatesWhenOtherThanPositionChanged) {
+TEST_F(LegacyTouchEventQueueTest, PointerStatesWhenOtherThanPositionChanged) {
   PressTouchPoint(1, 1);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
 
@@ -2509,7 +2503,7 @@
 }
 
 // Tests that TouchMoves are filtered when none of the points are changed.
-TEST_F(TouchEventQueueTest, FilterTouchMovesWhenNoPointerChanged) {
+TEST_F(LegacyTouchEventQueueTest, FilterTouchMovesWhenNoPointerChanged) {
   PressTouchPoint(1, 1);
   PressTouchPoint(2, 2);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
@@ -2562,7 +2556,7 @@
 }
 
 // Tests that touch-scroll-notification is not pushed into an empty queue.
-TEST_F(TouchEventQueueTest, TouchScrollNotificationOrder_EmptyQueue) {
+TEST_F(LegacyTouchEventQueueTest, TouchScrollNotificationOrder_EmptyQueue) {
   PrependTouchScrollNotification();
 
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
@@ -2572,7 +2566,7 @@
 
 // Tests touch-scroll-notification firing order when the event is placed at the
 // end of touch queue because of a pending ack for the head of the queue.
-TEST_F(TouchEventQueueTest, TouchScrollNotificationOrder_EndOfQueue) {
+TEST_F(LegacyTouchEventQueueTest, TouchScrollNotificationOrder_EndOfQueue) {
   PressTouchPoint(1, 1);
 
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
@@ -2604,7 +2598,7 @@
 
 // Tests touch-scroll-notification firing order when the event is placed in the
 // 2nd position in the touch queue between two events.
-TEST_F(TouchEventQueueTest, TouchScrollNotificationOrder_SecondPosition) {
+TEST_F(LegacyTouchEventQueueTest, TouchScrollNotificationOrder_SecondPosition) {
   PressTouchPoint(1, 1);
   MoveTouchPoint(0, 5, 5);
   ReleaseTouchPoint(0);
@@ -2654,7 +2648,7 @@
 
 // Tests that if touchStartOrFirstTouchMove is correctly set up for touch
 // events.
-TEST_F(TouchEventQueueTest, TouchStartOrFirstTouchMove) {
+TEST_F(LegacyTouchEventQueueTest, TouchStartOrFirstTouchMove) {
   PressTouchPoint(1, 1);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
   EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type());
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
new file mode 100644
index 0000000..fb5a0f4
--- /dev/null
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
@@ -0,0 +1,382 @@
+// 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 "content/browser/renderer_host/input/passthrough_touch_event_queue.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+#include "content/browser/renderer_host/input/touch_timeout_handler.h"
+#include "content/common/input/web_touch_event_traits.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/gfx/geometry/point_f.h"
+
+using blink::WebInputEvent;
+using blink::WebTouchEvent;
+using blink::WebTouchPoint;
+using ui::LatencyInfo;
+
+namespace content {
+namespace {
+
+// Compare all properties of touch points to determine the state.
+bool HasPointChanged(const WebTouchPoint& point_1,
+                     const WebTouchPoint& point_2) {
+  DCHECK_EQ(point_1.id, point_2.id);
+  if (point_1.screenPosition != point_2.screenPosition ||
+      point_1.position != point_2.position ||
+      point_1.radiusX != point_2.radiusX ||
+      point_1.radiusY != point_2.radiusY ||
+      point_1.rotationAngle != point_2.rotationAngle ||
+      point_1.force != point_2.force || point_1.tiltX != point_2.tiltX ||
+      point_1.tiltY != point_2.tiltY) {
+    return true;
+  }
+  return false;
+}
+
+}  // namespace
+
+PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState::
+    TouchEventWithLatencyInfoAndAckState(const TouchEventWithLatencyInfo& event)
+    : TouchEventWithLatencyInfo(event),
+      ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {}
+
+bool PassthroughTouchEventQueue::TouchEventWithLatencyInfoAndAckState::
+operator<(const TouchEventWithLatencyInfoAndAckState& other) const {
+  return event.uniqueTouchEventId < other.event.uniqueTouchEventId;
+}
+
+PassthroughTouchEventQueue::PassthroughTouchEventQueue(
+    TouchEventQueueClient* client,
+    const Config& config)
+    : client_(client),
+      has_handlers_(true),
+      maybe_has_handler_for_current_sequence_(false),
+      drop_remaining_touches_in_sequence_(false),
+      send_touch_events_async_(false) {
+  if (config.touch_ack_timeout_supported) {
+    timeout_handler_.reset(
+        new TouchTimeoutHandler(this, config.desktop_touch_ack_timeout_delay,
+                                config.mobile_touch_ack_timeout_delay));
+  }
+}
+
+PassthroughTouchEventQueue::~PassthroughTouchEventQueue() {}
+
+void PassthroughTouchEventQueue::SendTouchCancelEventForTouchEvent(
+    const TouchEventWithLatencyInfo& event_to_cancel) {
+  TouchEventWithLatencyInfo event = event_to_cancel;
+  WebTouchEventTraits::ResetTypeAndTouchStates(
+      WebInputEvent::TouchCancel,
+      // TODO(rbyers): Shouldn't we use a fresh timestamp?
+      event.event.timeStampSeconds(), &event.event);
+  SendTouchEventImmediately(&event, false);
+}
+
+void PassthroughTouchEventQueue::QueueEvent(
+    const TouchEventWithLatencyInfo& event) {
+  TRACE_EVENT0("input", "PassthroughTouchEventQueue::QueueEvent");
+  PreFilterResult filter_result = FilterBeforeForwarding(event.event);
+  if (filter_result != FORWARD_TO_RENDERER) {
+    client_->OnFilteringTouchEvent(event.event);
+
+    InputEventAckState ack_state =
+        filter_result == ACK_WITH_NO_CONSUMER_EXISTS
+            ? INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
+            : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
+    TouchEventWithLatencyInfoAndAckState event_with_ack_state = event;
+    event_with_ack_state.set_ack_state(ack_state);
+    outstanding_touches_.insert(event_with_ack_state);
+    AckCompletedEvents();
+    return;
+  }
+  TouchEventWithLatencyInfo cloned_event(event);
+  SendTouchEventImmediately(&cloned_event, true);
+}
+
+void PassthroughTouchEventQueue::PrependTouchScrollNotification() {
+  TRACE_EVENT0("input",
+               "PassthroughTouchEventQueue::PrependTouchScrollNotification");
+
+  TouchEventWithLatencyInfo touch(
+      WebInputEvent::TouchScrollStarted, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()), LatencyInfo());
+  touch.event.dispatchType = WebInputEvent::EventNonBlocking;
+  SendTouchEventImmediately(&touch, true);
+}
+
+void PassthroughTouchEventQueue::ProcessTouchAck(
+    InputEventAckState ack_result,
+    const LatencyInfo& latency_info,
+    const uint32_t unique_touch_event_id) {
+  TRACE_EVENT0("input", "PassthroughTouchEventQueue::ProcessTouchAck");
+  if (timeout_handler_ &&
+      timeout_handler_->ConfirmTouchEvent(unique_touch_event_id, ack_result))
+    return;
+
+  auto touch_event_iter = outstanding_touches_.begin();
+  while (touch_event_iter != outstanding_touches_.end()) {
+    if (unique_touch_event_id == touch_event_iter->event.uniqueTouchEventId)
+      break;
+    ++touch_event_iter;
+  }
+
+  if (touch_event_iter == outstanding_touches_.end())
+    return;
+
+  TouchEventWithLatencyInfoAndAckState event = *touch_event_iter;
+  touch_event_iter = outstanding_touches_.erase(touch_event_iter);
+  event.latency.AddNewLatencyFrom(latency_info);
+  event.set_ack_state(ack_result);
+  outstanding_touches_.insert(touch_event_iter, event);
+
+  AckCompletedEvents();
+}
+
+void PassthroughTouchEventQueue::OnGestureScrollEvent(
+    const GestureEventWithLatencyInfo& gesture_event) {
+  if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
+      gesture_event.event.resendingPluginId == -1) {
+    send_touch_events_async_ = true;
+  }
+}
+
+void PassthroughTouchEventQueue::OnGestureEventAck(
+    const GestureEventWithLatencyInfo& event,
+    InputEventAckState ack_result) {
+  // Turn events sent during gesture scrolls to be async.
+  if (event.event.type() == blink::WebInputEvent::GestureScrollUpdate &&
+      event.event.resendingPluginId == -1) {
+    send_touch_events_async_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
+  }
+}
+
+void PassthroughTouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
+  has_handlers_ = has_handlers;
+}
+
+bool PassthroughTouchEventQueue::IsPendingAckTouchStart() const {
+  if (outstanding_touches_.empty())
+    return false;
+
+  for (auto& iter : outstanding_touches_) {
+    if (iter.event.type() == WebInputEvent::TouchStart)
+      return true;
+  }
+  return false;
+}
+
+void PassthroughTouchEventQueue::SetAckTimeoutEnabled(bool enabled) {
+  if (timeout_handler_)
+    timeout_handler_->SetEnabled(enabled);
+}
+
+void PassthroughTouchEventQueue::SetIsMobileOptimizedSite(
+    bool mobile_optimized_site) {
+  if (timeout_handler_)
+    timeout_handler_->SetUseMobileTimeout(mobile_optimized_site);
+}
+
+bool PassthroughTouchEventQueue::IsAckTimeoutEnabled() const {
+  return timeout_handler_ && timeout_handler_->IsEnabled();
+}
+
+bool PassthroughTouchEventQueue::Empty() const {
+  return outstanding_touches_.empty();
+}
+
+void PassthroughTouchEventQueue::FlushQueue() {
+  drop_remaining_touches_in_sequence_ = true;
+  while (!outstanding_touches_.empty()) {
+    auto iter = outstanding_touches_.begin();
+    TouchEventWithLatencyInfoAndAckState event = *iter;
+    outstanding_touches_.erase(iter);
+    if (event.ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN)
+      event.set_ack_state(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+    AckTouchEventToClient(event, event.ack_state());
+  }
+}
+
+void PassthroughTouchEventQueue::AckCompletedEvents() {
+  while (!outstanding_touches_.empty()) {
+    auto iter = outstanding_touches_.begin();
+    if (iter->ack_state() == INPUT_EVENT_ACK_STATE_UNKNOWN)
+      break;
+    TouchEventWithLatencyInfoAndAckState event = *iter;
+    outstanding_touches_.erase(iter);
+    AckTouchEventToClient(event, event.ack_state());
+  }
+}
+
+void PassthroughTouchEventQueue::AckTouchEventToClient(
+    const TouchEventWithLatencyInfo& acked_event,
+    InputEventAckState ack_result) {
+  UpdateTouchConsumerStates(acked_event.event, ack_result);
+
+  // Skip ack for TouchScrollStarted since it was synthesized within the queue.
+  if (acked_event.event.type() != WebInputEvent::TouchScrollStarted) {
+    client_->OnTouchEventAck(acked_event, ack_result);
+  }
+}
+
+void PassthroughTouchEventQueue::SendTouchEventImmediately(
+    TouchEventWithLatencyInfo* touch,
+    bool wait_for_ack) {
+  // Note: Touchstart events are marked cancelable to allow transitions between
+  // platform scrolling and JS pinching. Touchend events, however, remain
+  // uncancelable, mitigating the risk of jank when transitioning to a fling.
+  if (send_touch_events_async_ &&
+      touch->event.type() != WebInputEvent::TouchStart)
+    touch->event.dispatchType = WebInputEvent::EventNonBlocking;
+
+  if (touch->event.type() == WebInputEvent::TouchStart)
+    touch->event.touchStartOrFirstTouchMove = true;
+
+  // For touchmove events, compare touch points position from current event
+  // to last sent event and update touch points state.
+  if (touch->event.type() == WebInputEvent::TouchMove) {
+    CHECK(last_sent_touchevent_);
+    if (last_sent_touchevent_->type() == WebInputEvent::TouchStart)
+      touch->event.touchStartOrFirstTouchMove = true;
+    for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
+      const WebTouchPoint& last_touch_point = last_sent_touchevent_->touches[i];
+      // Touches with same id may not have same index in Touches array.
+      for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
+        const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
+        if (current_touchmove_point.id != last_touch_point.id)
+          continue;
+
+        if (!HasPointChanged(last_touch_point, current_touchmove_point))
+          touch->event.touches[j].state = WebTouchPoint::StateStationary;
+
+        break;
+      }
+    }
+  }
+
+  if (touch->event.type() != WebInputEvent::TouchScrollStarted) {
+    if (last_sent_touchevent_)
+      *last_sent_touchevent_ = touch->event;
+    else
+      last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
+  }
+
+  if (timeout_handler_)
+    timeout_handler_->StartIfNecessary(*touch);
+  if (wait_for_ack)
+    outstanding_touches_.insert(*touch);
+  client_->SendTouchEventImmediately(*touch);
+}
+
+PassthroughTouchEventQueue::PreFilterResult
+PassthroughTouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
+  if (event.type() == WebInputEvent::TouchScrollStarted)
+    return FORWARD_TO_RENDERER;
+
+  if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
+    // We don't know if we have a handler until we get the ACK back so
+    // assume it is true.
+    maybe_has_handler_for_current_sequence_ = true;
+    send_touch_events_async_ = false;
+    last_sent_touchevent_.reset();
+
+    drop_remaining_touches_in_sequence_ = false;
+    if (!has_handlers_) {
+      drop_remaining_touches_in_sequence_ = true;
+      return ACK_WITH_NO_CONSUMER_EXISTS;
+    }
+  }
+
+  if (timeout_handler_ && timeout_handler_->FilterEvent(event))
+    return ACK_WITH_NO_CONSUMER_EXISTS;
+
+  if (drop_remaining_touches_in_sequence_ &&
+      event.type() != WebInputEvent::TouchCancel) {
+    return ACK_WITH_NO_CONSUMER_EXISTS;
+  }
+
+  if (event.type() == WebInputEvent::TouchStart) {
+    return (has_handlers_ || maybe_has_handler_for_current_sequence_)
+               ? FORWARD_TO_RENDERER
+               : ACK_WITH_NO_CONSUMER_EXISTS;
+  }
+
+  if (maybe_has_handler_for_current_sequence_) {
+    // Only forward a touch if it has a non-stationary pointer that is active
+    // in the current touch sequence.
+    for (size_t i = 0; i < event.touchesLength; ++i) {
+      const WebTouchPoint& point = event.touches[i];
+      if (point.state == WebTouchPoint::StateStationary)
+        continue;
+
+      // |last_sent_touchevent_| will be non-null as long as there is an
+      // active touch sequence being forwarded to the renderer.
+      if (!last_sent_touchevent_)
+        continue;
+
+      for (size_t j = 0; j < last_sent_touchevent_->touchesLength; ++j) {
+        if (point.id != last_sent_touchevent_->touches[j].id)
+          continue;
+
+        if (event.type() != WebInputEvent::TouchMove)
+          return FORWARD_TO_RENDERER;
+
+        // All pointers in TouchMove events may have state as StateMoved,
+        // even though none of the pointers have not changed in real.
+        // Forward these events when at least one pointer has changed.
+        if (HasPointChanged(last_sent_touchevent_->touches[j], point))
+          return FORWARD_TO_RENDERER;
+
+        // This is a TouchMove event for which we have yet to find a
+        // non-stationary pointer. Continue checking the next pointers
+        // in the |event|.
+        break;
+      }
+    }
+  }
+
+  return ACK_WITH_NO_CONSUMER_EXISTS;
+}
+
+void PassthroughTouchEventQueue::UpdateTouchConsumerStates(
+    const WebTouchEvent& event,
+    InputEventAckState ack_result) {
+  if (event.type() == WebInputEvent::TouchStart) {
+    if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
+      send_touch_events_async_ = false;
+
+    // Once we have the ack back for the sequence we know if there
+    // is a handler or not. Other touch-starts sent can upgrade
+    // whether we have a handler or not as well.
+    if (WebTouchEventTraits::IsTouchSequenceStart(event)) {
+      maybe_has_handler_for_current_sequence_ =
+          ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
+    } else {
+      maybe_has_handler_for_current_sequence_ |=
+          ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
+    }
+  } else if (WebTouchEventTraits::IsTouchSequenceEnd(event)) {
+    maybe_has_handler_for_current_sequence_ = false;
+  }
+}
+
+size_t PassthroughTouchEventQueue::SizeForTesting() const {
+  return outstanding_touches_.size();
+}
+
+bool PassthroughTouchEventQueue::IsTimeoutRunningForTesting() const {
+  return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
+}
+
+const TouchEventWithLatencyInfo&
+PassthroughTouchEventQueue::GetLatestEventForTesting() const {
+  return *outstanding_touches_.rbegin();
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.h b/content/browser/renderer_host/input/passthrough_touch_event_queue.h
new file mode 100644
index 0000000..598e9b8
--- /dev/null
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.h
@@ -0,0 +1,137 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_
+
+#include "content/browser/renderer_host/input/touch_event_queue.h"
+
+#include <set>
+
+namespace content {
+
+class TouchTimeoutHandler;
+
+// A queue that processes a touch-event and forwards it on to the
+// renderer process immediately. This class assumes that queueing will
+// happen inside the renderer process. This class will hold onto the pending
+// events so that it can re-order the acks so that they appear in a
+// logical order to the rest of the browser process. Due to the threaded
+// model of the renderer it is possible that an ack for a touchend can
+// be sent before the corresponding ack for the touchstart. This class
+// corrects that state.
+class CONTENT_EXPORT PassthroughTouchEventQueue : public TouchEventQueue {
+ public:
+  PassthroughTouchEventQueue(TouchEventQueueClient* client,
+                             const Config& config);
+
+  ~PassthroughTouchEventQueue() override;
+
+  // TouchEventQueue overrides.
+  void QueueEvent(const TouchEventWithLatencyInfo& event) override;
+
+  void PrependTouchScrollNotification() override;
+
+  void ProcessTouchAck(InputEventAckState ack_result,
+                       const ui::LatencyInfo& latency_info,
+                       const uint32_t unique_touch_event_id) override;
+  void OnGestureScrollEvent(
+      const GestureEventWithLatencyInfo& gesture_event) override;
+
+  void OnGestureEventAck(const GestureEventWithLatencyInfo& event,
+                         InputEventAckState ack_result) override;
+
+  void OnHasTouchEventHandlers(bool has_handlers) override;
+
+  bool IsPendingAckTouchStart() const override;
+
+  void SetAckTimeoutEnabled(bool enabled) override;
+
+  void SetIsMobileOptimizedSite(bool mobile_optimized_site) override;
+
+  bool IsAckTimeoutEnabled() const override;
+
+  bool Empty() const override;
+
+ protected:
+  void SendTouchCancelEventForTouchEvent(
+      const TouchEventWithLatencyInfo& event_to_cancel) override;
+  void UpdateTouchConsumerStates(const blink::WebTouchEvent& event,
+                                 InputEventAckState ack_result) override;
+  // Empties the queue of touch events. This may result in any number of gesture
+  // events being sent to the renderer.
+  void FlushQueue() override;
+
+ private:
+  friend class PassthroughTouchEventQueueTest;
+
+  class TouchEventWithLatencyInfoAndAckState
+      : public TouchEventWithLatencyInfo {
+   public:
+    TouchEventWithLatencyInfoAndAckState(const TouchEventWithLatencyInfo&);
+    bool operator<(const TouchEventWithLatencyInfoAndAckState&) const;
+    InputEventAckState ack_state() const { return ack_state_; }
+    void set_ack_state(InputEventAckState state) { ack_state_ = state; }
+
+   private:
+    InputEventAckState ack_state_;
+  };
+
+  enum PreFilterResult {
+    ACK_WITH_NO_CONSUMER_EXISTS,
+    ACK_WITH_NOT_CONSUMED,
+    FORWARD_TO_RENDERER,
+  };
+  // Filter touches prior to forwarding to the renderer, e.g., if the renderer
+  // has no touch handler.
+  PreFilterResult FilterBeforeForwarding(const blink::WebTouchEvent& event);
+
+  void AckTouchEventToClient(const TouchEventWithLatencyInfo& acked_event,
+                             InputEventAckState ack_result);
+
+  void SendTouchEventImmediately(TouchEventWithLatencyInfo* touch,
+                                 bool wait_for_ack);
+
+  void AckCompletedEvents();
+
+  bool IsTimeoutRunningForTesting() const;
+  const TouchEventWithLatencyInfo& GetLatestEventForTesting() const;
+  size_t SizeForTesting() const;
+
+  // Handles touch event forwarding and ack'ed event dispatch.
+  TouchEventQueueClient* client_;
+
+  // Whether the renderer has at least one touch handler.
+  bool has_handlers_;
+
+  // Whether any pointer in the touch sequence may have having a consumer.
+  bool maybe_has_handler_for_current_sequence_;
+
+  // Whether to allow any remaining touches for the current sequence. Note that
+  // this is a stricter condition than an empty |touch_consumer_states_|, as it
+  // also prevents forwarding of touchstart events for new pointers in the
+  // current sequence. This is only used when the event is synthetically
+  // cancelled after a touch timeout.
+  bool drop_remaining_touches_in_sequence_;
+
+  // Optional handler for timed-out touch event acks.
+  std::unique_ptr<TouchTimeoutHandler> timeout_handler_;
+
+  // Whether touch events should be sent as uncancelable or not.
+  bool send_touch_events_async_;
+
+  // Event is saved to compare pointer positions for new touchmove events.
+  std::unique_ptr<blink::WebTouchEvent> last_sent_touchevent_;
+
+  // Stores outstanding touches that have been sent to the renderer but have
+  // not yet been ack'd by the renderer. The set is explicitly ordered based
+  // on the unique touch event id.
+  std::set<TouchEventWithLatencyInfoAndAckState> outstanding_touches_;
+
+  DISALLOW_COPY_AND_ASSIGN(PassthroughTouchEventQueue);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc
new file mode 100644
index 0000000..6b708cc
--- /dev/null
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc
@@ -0,0 +1,1733 @@
+// 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 "content/browser/renderer_host/input/passthrough_touch_event_queue.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/browser/renderer_host/input/timeout_monitor.h"
+#include "content/common/input/synthetic_web_input_event_builders.h"
+#include "content/common/input/web_touch_event_traits.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebInputEvent.h"
+#include "ui/events/base_event_utils.h"
+
+using blink::WebGestureEvent;
+using blink::WebInputEvent;
+using blink::WebTouchEvent;
+using blink::WebTouchPoint;
+
+namespace content {
+namespace {
+
+const double kMinSecondsBetweenThrottledTouchmoves = 0.2;
+const float kSlopLengthDips = 10;
+const float kHalfSlopLengthDips = kSlopLengthDips / 2;
+
+base::TimeDelta DefaultTouchTimeoutDelay() {
+  return base::TimeDelta::FromMilliseconds(1);
+}
+}  // namespace
+
+class PassthroughTouchEventQueueTest : public testing::Test,
+                                       public TouchEventQueueClient {
+ public:
+  PassthroughTouchEventQueueTest()
+      : acked_event_count_(0),
+        last_acked_event_state_(INPUT_EVENT_ACK_STATE_UNKNOWN),
+        slop_length_dips_(0) {}
+
+  ~PassthroughTouchEventQueueTest() override {}
+
+  // testing::Test
+  void SetUp() override {
+    ResetQueueWithConfig(TouchEventQueue::Config());
+    sent_events_ids_.clear();
+  }
+
+  void TearDown() override { queue_.reset(); }
+
+  // TouchEventQueueClient
+  void SendTouchEventImmediately(
+      const TouchEventWithLatencyInfo& event) override {
+    sent_events_.push_back(event.event);
+    sent_events_ids_.push_back(event.event.uniqueTouchEventId);
+    if (sync_ack_result_) {
+      auto sync_ack_result = std::move(sync_ack_result_);
+      SendTouchEventAck(*sync_ack_result);
+    }
+  }
+
+  void OnTouchEventAck(const TouchEventWithLatencyInfo& event,
+                       InputEventAckState ack_result) override {
+    ++acked_event_count_;
+    last_acked_event_ = event.event;
+    last_acked_event_state_ = ack_result;
+    if (followup_touch_event_) {
+      std::unique_ptr<WebTouchEvent> followup_touch_event =
+          std::move(followup_touch_event_);
+      SendTouchEvent(*followup_touch_event);
+    }
+    if (followup_gesture_event_) {
+      std::unique_ptr<WebGestureEvent> followup_gesture_event =
+          std::move(followup_gesture_event_);
+      queue_->OnGestureScrollEvent(GestureEventWithLatencyInfo(
+          *followup_gesture_event, ui::LatencyInfo()));
+    }
+  }
+
+  void OnFilteringTouchEvent(const blink::WebTouchEvent& touch_event) override {
+  }
+
+ protected:
+  void SetUpForTouchMoveSlopTesting(double slop_length_dips) {
+    slop_length_dips_ = slop_length_dips;
+  }
+
+  void SetUpForTimeoutTesting(base::TimeDelta desktop_timeout_delay,
+                              base::TimeDelta mobile_timeout_delay) {
+    TouchEventQueue::Config config;
+    config.desktop_touch_ack_timeout_delay = desktop_timeout_delay;
+    config.mobile_touch_ack_timeout_delay = mobile_timeout_delay;
+    config.touch_ack_timeout_supported = true;
+    ResetQueueWithConfig(config);
+  }
+
+  void SetUpForTimeoutTesting() {
+    SetUpForTimeoutTesting(DefaultTouchTimeoutDelay(),
+                           DefaultTouchTimeoutDelay());
+  }
+
+  void SendTouchEvent(WebTouchEvent event) {
+    if (slop_length_dips_) {
+      event.movedBeyondSlopRegion = false;
+      if (WebTouchEventTraits::IsTouchSequenceStart(event))
+        anchor_ = event.touches[0].position;
+      if (event.type() == WebInputEvent::TouchMove) {
+        gfx::Vector2dF delta = anchor_ - event.touches[0].position;
+        if (delta.LengthSquared() > slop_length_dips_ * slop_length_dips_)
+          event.movedBeyondSlopRegion = true;
+      }
+    } else {
+      event.movedBeyondSlopRegion = event.type() == WebInputEvent::TouchMove;
+    }
+    queue_->QueueEvent(TouchEventWithLatencyInfo(event, ui::LatencyInfo()));
+  }
+
+  void SendGestureEvent(WebInputEvent::Type type) {
+    WebGestureEvent event(type, WebInputEvent::NoModifiers,
+                          ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+    queue_->OnGestureScrollEvent(
+        GestureEventWithLatencyInfo(event, ui::LatencyInfo()));
+  }
+
+  void SendTouchEventAck(InputEventAckState ack_result) {
+    DCHECK(!sent_events_ids_.empty());
+    queue_->ProcessTouchAck(ack_result, ui::LatencyInfo(),
+                            sent_events_ids_.front());
+    sent_events_ids_.pop_front();
+  }
+
+  void SendTouchEventAckLast(InputEventAckState ack_result) {
+    DCHECK(!sent_events_ids_.empty());
+    queue_->ProcessTouchAck(ack_result, ui::LatencyInfo(),
+                            sent_events_ids_.back());
+    sent_events_ids_.pop_back();
+  }
+
+  void SendTouchEventAckWithID(InputEventAckState ack_result,
+                               int unique_event_id) {
+    queue_->ProcessTouchAck(ack_result, ui::LatencyInfo(), unique_event_id);
+    sent_events_ids_.erase(std::remove(sent_events_ids_.begin(),
+                                       sent_events_ids_.end(), unique_event_id),
+                           sent_events_ids_.end());
+  }
+
+  void SendGestureEventAck(WebInputEvent::Type type,
+                           InputEventAckState ack_result) {
+    GestureEventWithLatencyInfo event(
+        type, blink::WebInputEvent::NoModifiers,
+        ui::EventTimeStampToSeconds(ui::EventTimeForNow()), ui::LatencyInfo());
+    queue_->OnGestureEventAck(event, ack_result);
+  }
+
+  void SetFollowupEvent(const WebTouchEvent& event) {
+    followup_touch_event_.reset(new WebTouchEvent(event));
+  }
+
+  void SetFollowupEvent(const WebGestureEvent& event) {
+    followup_gesture_event_.reset(new WebGestureEvent(event));
+  }
+
+  void SetSyncAckResult(InputEventAckState sync_ack_result) {
+    sync_ack_result_.reset(new InputEventAckState(sync_ack_result));
+  }
+
+  void PressTouchPoint(float x, float y) {
+    touch_event_.PressPoint(x, y);
+    SendTouchEvent();
+  }
+
+  void MoveTouchPoint(int index, float x, float y) {
+    touch_event_.MovePoint(index, x, y);
+    SendTouchEvent();
+  }
+
+  void MoveTouchPoints(int index0,
+                       float x0,
+                       float y0,
+                       int index1,
+                       float x1,
+                       float y1) {
+    touch_event_.MovePoint(index0, x0, y0);
+    touch_event_.MovePoint(index1, x1, y1);
+    SendTouchEvent();
+  }
+
+  void ChangeTouchPointRadius(int index, float radius_x, float radius_y) {
+    CHECK_GE(index, 0);
+    CHECK_LT(index, touch_event_.kTouchesLengthCap);
+    WebTouchPoint& point = touch_event_.touches[index];
+    point.radiusX = radius_x;
+    point.radiusY = radius_y;
+    touch_event_.touches[index].state = WebTouchPoint::StateMoved;
+    touch_event_.movedBeyondSlopRegion = true;
+    WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
+                                   touch_event_.timeStampSeconds(),
+                                   &touch_event_);
+    SendTouchEvent();
+  }
+
+  void ChangeTouchPointRotationAngle(int index, float rotation_angle) {
+    CHECK_GE(index, 0);
+    CHECK_LT(index, touch_event_.kTouchesLengthCap);
+    WebTouchPoint& point = touch_event_.touches[index];
+    point.rotationAngle = rotation_angle;
+    touch_event_.touches[index].state = WebTouchPoint::StateMoved;
+    touch_event_.movedBeyondSlopRegion = true;
+    WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
+                                   touch_event_.timeStampSeconds(),
+                                   &touch_event_);
+    SendTouchEvent();
+  }
+
+  void ChangeTouchPointForce(int index, float force) {
+    CHECK_GE(index, 0);
+    CHECK_LT(index, touch_event_.kTouchesLengthCap);
+    WebTouchPoint& point = touch_event_.touches[index];
+    point.force = force;
+    touch_event_.touches[index].state = WebTouchPoint::StateMoved;
+    touch_event_.movedBeyondSlopRegion = true;
+    WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
+                                   touch_event_.timeStampSeconds(),
+                                   &touch_event_);
+    SendTouchEvent();
+  }
+
+  void ReleaseTouchPoint(int index) {
+    touch_event_.ReleasePoint(index);
+    SendTouchEvent();
+  }
+
+  void CancelTouchPoint(int index) {
+    touch_event_.CancelPoint(index);
+    SendTouchEvent();
+  }
+
+  void PrependTouchScrollNotification() {
+    queue_->PrependTouchScrollNotification();
+  }
+
+  void AdvanceTouchTime(double seconds) {
+    touch_event_.setTimeStampSeconds(touch_event_.timeStampSeconds() + seconds);
+  }
+
+  void ResetTouchEvent() { touch_event_ = SyntheticWebTouchEvent(); }
+
+  size_t GetAndResetAckedEventCount() {
+    size_t count = acked_event_count_;
+    acked_event_count_ = 0;
+    return count;
+  }
+
+  size_t GetAndResetSentEventCount() {
+    size_t count = sent_events_.size();
+    sent_events_.clear();
+    return count;
+  }
+
+  bool IsPendingAckTouchStart() const {
+    return queue_->IsPendingAckTouchStart();
+  }
+
+  void OnHasTouchEventHandlers(bool has_handlers) {
+    queue_->OnHasTouchEventHandlers(has_handlers);
+  }
+
+  void SetAckTimeoutDisabled() { queue_->SetAckTimeoutEnabled(false); }
+
+  void SetIsMobileOptimizedSite(bool is_mobile_optimized) {
+    queue_->SetIsMobileOptimizedSite(is_mobile_optimized);
+  }
+
+  bool IsTimeoutRunning() const { return queue_->IsTimeoutRunningForTesting(); }
+
+  size_t queued_event_count() const { return queue_->SizeForTesting(); }
+
+  const WebTouchEvent& latest_event() const {
+    return queue_->GetLatestEventForTesting().event;
+  }
+
+  const WebTouchEvent& acked_event() const { return last_acked_event_; }
+
+  const WebTouchEvent& sent_event() const {
+    DCHECK(!sent_events_.empty());
+    return sent_events_.back();
+  }
+
+  const std::vector<WebTouchEvent>& all_sent_events() const {
+    return sent_events_;
+  }
+
+  InputEventAckState acked_event_state() const {
+    return last_acked_event_state_;
+  }
+
+  static void RunTasksAndWait(base::TimeDelta delay) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), delay);
+    base::RunLoop().Run();
+  }
+
+  int GetUniqueTouchEventID() { return sent_events_ids_.back(); }
+
+ private:
+  void SendTouchEvent() {
+    SendTouchEvent(touch_event_);
+    touch_event_.ResetPoints();
+  }
+
+  void ResetQueueWithConfig(const TouchEventQueue::Config& config) {
+    queue_.reset(new PassthroughTouchEventQueue(this, config));
+    queue_->OnHasTouchEventHandlers(true);
+  }
+
+  std::unique_ptr<PassthroughTouchEventQueue> queue_;
+  size_t acked_event_count_;
+  WebTouchEvent last_acked_event_;
+  std::vector<WebTouchEvent> sent_events_;
+  InputEventAckState last_acked_event_state_;
+  SyntheticWebTouchEvent touch_event_;
+  std::unique_ptr<WebTouchEvent> followup_touch_event_;
+  std::unique_ptr<WebGestureEvent> followup_gesture_event_;
+  std::unique_ptr<InputEventAckState> sync_ack_result_;
+  double slop_length_dips_;
+  gfx::PointF anchor_;
+  base::MessageLoopForUI message_loop_;
+  std::deque<int> sent_events_ids_;
+};
+
+// Tests that touch-events are queued properly.
+TEST_F(PassthroughTouchEventQueueTest, Basic) {
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The second touch should be sent right away even though
+  // we haven't received an ack for the touch start.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Receive an ACK for the first touch-event.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type());
+  EXPECT_EQ(WebInputEvent::Blocking, acked_event().dispatchType);
+
+  // Receive an ACK for the second touch-event.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(WebInputEvent::TouchMove, acked_event().type());
+  EXPECT_EQ(WebInputEvent::Blocking, acked_event().dispatchType);
+}
+
+// Tests that touch-events with multiple points are queued properly.
+TEST_F(PassthroughTouchEventQueueTest, BasicMultiTouch) {
+  const size_t kPointerCount = 10;
+  for (float i = 0; i < kPointerCount; ++i)
+    PressTouchPoint(i, i);
+
+  EXPECT_EQ(kPointerCount, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount, queued_event_count());
+
+  for (int i = 0; i < static_cast<int>(kPointerCount); ++i)
+    MoveTouchPoint(i, 1.f + i, 2.f + i);
+
+  EXPECT_EQ(kPointerCount, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  // No coalescing happens in the queue since they are all sent immediately.
+  EXPECT_EQ(2 * kPointerCount, queued_event_count());
+
+  for (int i = 0; i < static_cast<int>(kPointerCount); ++i)
+    ReleaseTouchPoint(kPointerCount - 1 - i);
+
+  EXPECT_EQ(kPointerCount, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount * 3, queued_event_count());
+
+  // Ack all presses.
+  for (size_t i = 0; i < kPointerCount; ++i)
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Ack the touch moves.
+  for (size_t i = 0; i < kPointerCount; ++i)
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Ack all releases.
+  for (size_t i = 0; i < kPointerCount; ++i)
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+}
+
+// Tests that the touch-queue continues delivering events for an active touch
+// sequence after all handlers are removed.
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchesForwardedIfHandlerRemovedDuringSequence) {
+  OnHasTouchEventHandlers(true);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Send a touch-press event.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Signal that all touch handlers have been removed.
+  OnHasTouchEventHandlers(false);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Process the ack for the sent touch, ensuring that it is honored (despite
+  // the touch handler having been removed).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
+
+  // Try forwarding a new pointer. It should be forwarded as usual.
+  PressTouchPoint(2, 2);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // Further events for any pointer should be forwarded, even for pointers that
+  // reported no consumer.
+  MoveTouchPoint(1, 3, 3);
+  ReleaseTouchPoint(1);
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Events for the first pointer, that had a handler, should be forwarded.
+  MoveTouchPoint(0, 4, 4);
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
+}
+
+// Tests that addition of a touch handler during a touch sequence will not cause
+// the remaining sequence to be forwarded.
+TEST_F(PassthroughTouchEventQueueTest,
+       ActiveSequenceNotForwardedWhenHandlersAdded) {
+  OnHasTouchEventHandlers(false);
+
+  // Send a touch-press event while there is no handler.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  OnHasTouchEventHandlers(true);
+
+  // The remaining touch sequence should not be forwarded.
+  MoveTouchPoint(0, 5, 5);
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // A new touch sequence should resume forwarding.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+}
+
+// Tests that removal of a touch handler during a touch sequence will prevent
+// the remaining sequence from being forwarded, even if another touch handler is
+// registered during the same touch sequence.
+TEST_F(PassthroughTouchEventQueueTest,
+       ActiveSequenceDroppedWhenHandlersRemoved) {
+  // Send a touch-press event.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Queue a touch-move event.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Unregister all touch handlers.
+  OnHasTouchEventHandlers(false);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Repeated registration/unregstration of handlers should have no effect as
+  // we're still awaiting the ack arrival.
+  OnHasTouchEventHandlers(true);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+  OnHasTouchEventHandlers(false);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // clear the queue .
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // Events should be dropped while there is no touch handler.
+  MoveTouchPoint(0, 10, 10);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Simulate touch handler registration in the middle of a touch sequence.
+  OnHasTouchEventHandlers(true);
+
+  // The touch end for the interrupted sequence should be dropped.
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // A new touch sequence should be forwarded properly.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+}
+
+// Tests that removal/addition of a touch handler without any intervening
+// touch activity has no affect on touch forwarding.
+TEST_F(PassthroughTouchEventQueueTest,
+       ActiveSequenceUnaffectedByRepeatedHandlerRemovalAndAddition) {
+  // Send a touch-press event.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Simulate the case where the touchstart handler removes itself, and adds a
+  // touchmove handler.
+  OnHasTouchEventHandlers(false);
+  OnHasTouchEventHandlers(true);
+
+  // Queue a touch-move event, should be sent right away.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The ack should trigger forwarding of the touchmove, as if no touch
+  // handler registration changes have occurred.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+}
+
+// Tests that the touch-event queue is robust to redundant acks.
+TEST_F(PassthroughTouchEventQueueTest, SpuriousAcksIgnored) {
+  // Trigger a spurious ack.
+  SendTouchEventAckWithID(INPUT_EVENT_ACK_STATE_CONSUMED, 0);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Send and ack a touch press.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  // Trigger a spurious ack.
+  SendTouchEventAckWithID(INPUT_EVENT_ACK_STATE_CONSUMED, 3);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that touch-move events are not sent to the renderer if the preceding
+// touch-press event did not have a consumer (and consequently, did not hit the
+// main thread in the renderer).
+TEST_F(PassthroughTouchEventQueueTest, NoConsumer) {
+  // The first touch-press should reach the renderer.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The second touch should be sent since we don't know if there is
+  // a consumer or not.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Receive an ACK for the first touch-event and the first touch-move
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Send a release event. This should not reach the renderer.
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(WebInputEvent::TouchEnd, acked_event().type());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Send a press-event, followed by move a following move should not
+  // be sent but held in the queue.
+  PressTouchPoint(10, 10);
+  MoveTouchPoint(0, 5, 5);
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 6, 5);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+}
+
+TEST_F(PassthroughTouchEventQueueTest, AckTouchEventInReverse) {
+  PressTouchPoint(1, 1);
+  MoveTouchPoint(0, 5, 5);
+  MoveTouchPoint(0, 15, 15);
+  ReleaseTouchPoint(0);
+
+  EXPECT_EQ(4U, GetAndResetSentEventCount());
+  EXPECT_EQ(4U, queued_event_count());
+
+  SendTouchEventAckLast(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(4U, queued_event_count());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(3U, queued_event_count());
+
+  SendTouchEventAckLast(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(3U, queued_event_count());
+
+  SendTouchEventAckLast(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchEnd, acked_event().type());
+  EXPECT_EQ(3U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+}
+
+// Tests that touch-event's enqueued via a touch ack are properly handled.
+TEST_F(PassthroughTouchEventQueueTest, AckWithFollowupEvents) {
+  // Queue a touch down.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Create a touch event that will be queued synchronously by a touch ack.
+  // Note, this will be triggered by all subsequent touch acks.
+  WebTouchEvent followup_event(
+      WebInputEvent::TouchMove, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+  followup_event.touchesLength = 1;
+  followup_event.touches[0].id = 0;
+  followup_event.touches[0].state = WebTouchPoint::StateMoved;
+  SetFollowupEvent(followup_event);
+
+  // Receive an ACK for the press. This should cause the followup touch-move to
+  // be sent to the renderer.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
+  EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type());
+
+  // Queue another event.
+  MoveTouchPoint(0, 2, 2);
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Receive an ACK for the touch-move followup event. This should cause the
+  // subsequent touch move event be sent to the renderer.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that touch-events can be synchronously ack'ed.
+TEST_F(PassthroughTouchEventQueueTest, SynchronousAcks) {
+  // TouchStart
+  SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove
+  SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
+  MoveTouchPoint(0, 2, 2);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchEnd
+  SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchCancel (first inserting a TouchStart so the TouchCancel will be sent)
+  PressTouchPoint(1, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
+  CancelTouchPoint(0);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that followup events triggered by an immediate ack from
+// TouchEventQueue::QueueEvent() are properly handled.
+TEST_F(PassthroughTouchEventQueueTest, ImmediateAckWithFollowupEvents) {
+  // Create a touch event that will be queued synchronously by a touch ack.
+  WebTouchEvent followup_event(
+      WebInputEvent::TouchStart, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+  followup_event.touchesLength = 1;
+  followup_event.touches[0].id = 1;
+  followup_event.touches[0].state = WebTouchPoint::StatePressed;
+  SetFollowupEvent(followup_event);
+
+  // Now, enqueue a stationary touch that will not be forwarded.  This should be
+  // immediately ack'ed with "NO_CONSUMER_EXISTS".  The followup event should
+  // then be enqueued and immediately sent to the renderer.
+  WebTouchEvent stationary_event(
+      WebInputEvent::TouchMove, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+  ;
+  stationary_event.touchesLength = 1;
+  stationary_event.touches[0].id = 1;
+  stationary_event.touches[0].state = WebTouchPoint::StateStationary;
+  SendTouchEvent(stationary_event);
+
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+  EXPECT_EQ(WebInputEvent::TouchMove, acked_event().type());
+}
+
+// Tests basic TouchEvent forwarding suppression.
+TEST_F(PassthroughTouchEventQueueTest, NoTouchBasic) {
+  // Disable TouchEvent forwarding.
+  OnHasTouchEventHandlers(false);
+  PressTouchPoint(30, 5);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove should not be sent to renderer.
+  MoveTouchPoint(0, 65, 10);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchEnd should not be sent to renderer.
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Enable TouchEvent forwarding.
+  OnHasTouchEventHandlers(true);
+
+  PressTouchPoint(80, 10);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 80, 20);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that IsTouchStartPendingAck works correctly.
+TEST_F(PassthroughTouchEventQueueTest, PendingStart) {
+  EXPECT_FALSE(IsPendingAckTouchStart());
+
+  // Send the touchstart for one point (#1).
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Send a touchmove for that point (#2).
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Ack the touchstart (#1).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_FALSE(IsPendingAckTouchStart());
+
+  // Send a touchstart for another point (#3).
+  PressTouchPoint(10, 10);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Ack the touchmove (#2).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Send a touchstart for a third point (#4).
+  PressTouchPoint(15, 15);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Ack the touchstart for the second point (#3).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_TRUE(IsPendingAckTouchStart());
+
+  // Ack the touchstart for the third point (#4).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_FALSE(IsPendingAckTouchStart());
+}
+
+// Tests that the touch timeout is started when sending certain touch types.
+TEST_F(PassthroughTouchEventQueueTest, TouchTimeoutTypes) {
+  SetUpForTimeoutTesting();
+
+  // Sending a TouchStart will start the timeout.
+  PressTouchPoint(0, 1);
+  EXPECT_TRUE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchMove should start the timeout.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_TRUE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchEnd should not start the timeout.
+  ReleaseTouchPoint(0);
+  EXPECT_FALSE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchCancel should not start the timeout.
+  PressTouchPoint(0, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  ASSERT_FALSE(IsTimeoutRunning());
+  CancelTouchPoint(0);
+  EXPECT_FALSE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+}
+
+// Tests that a delayed TouchEvent ack will trigger a TouchCancel timeout,
+// disabling touch forwarding until the next TouchStart is received after
+// the timeout events are ack'ed.
+TEST_F(PassthroughTouchEventQueueTest, TouchTimeoutBasic) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  GetAndResetSentEventCount();
+  GetAndResetAckedEventCount();
+  PressTouchPoint(0, 1);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_TRUE(IsTimeoutRunning());
+
+  // Delay the ack.
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+
+  // The timeout should have fired, synthetically ack'ing the timed-out event.
+  // TouchEvent forwarding is disabled until the ack is received for the
+  // timed-out event and the future cancel event.
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Ack'ing the original event should trigger a cancel event.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(WebInputEvent::TouchCancel, sent_event().type());
+  EXPECT_NE(WebInputEvent::Blocking, sent_event().dispatchType);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Touch events should not be forwarded until we receive the cancel acks.
+  MoveTouchPoint(0, 1, 1);
+  ASSERT_EQ(0U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  ReleaseTouchPoint(0);
+  ASSERT_EQ(0U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // The synthetic TouchCancel ack should not reach the client, but should
+  // resume touch forwarding.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Subsequent events should be handled normally.
+  PressTouchPoint(0, 1);
+  EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type());
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that the timeout is never started if the renderer consumes
+// a TouchEvent from the current touch sequence.
+TEST_F(PassthroughTouchEventQueueTest,
+       NoTouchTimeoutIfRendererIsConsumingGesture) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  ASSERT_TRUE(IsTimeoutRunning());
+
+  // Mark the event as consumed. This should prevent the timeout from
+  // being activated on subsequent TouchEvents in this gesture.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchMove should not start the timeout.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_FALSE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // A secondary TouchStart should not start the timeout.
+  PressTouchPoint(1, 0);
+  EXPECT_FALSE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // A TouchEnd should not start the timeout.
+  ReleaseTouchPoint(1);
+  EXPECT_FALSE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // A TouchCancel should not start the timeout.
+  CancelTouchPoint(0);
+  EXPECT_FALSE(IsTimeoutRunning());
+}
+
+// Tests that the timeout is never started if the renderer consumes
+// a TouchEvent from the current touch sequence.
+TEST_F(PassthroughTouchEventQueueTest,
+       NoTouchTimeoutIfDisabledAfterTouchStart) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  ASSERT_TRUE(IsTimeoutRunning());
+
+  // Send the ack immediately. The timeout should not have fired.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now explicitly disable the timeout.
+  SetAckTimeoutDisabled();
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchMove should not start or trigger the timeout.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that the timeout is never started if the ack is synchronous.
+TEST_F(PassthroughTouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
+  ASSERT_FALSE(IsTimeoutRunning());
+  PressTouchPoint(0, 1);
+  EXPECT_FALSE(IsTimeoutRunning());
+}
+
+// Tests that the timeout does not fire if explicitly disabled while an event
+// is in-flight.
+TEST_F(PassthroughTouchEventQueueTest,
+       NoTouchTimeoutIfDisabledWhileTimerIsActive) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  ASSERT_TRUE(IsTimeoutRunning());
+
+  // Verify that disabling the timeout also turns off the timer.
+  SetAckTimeoutDisabled();
+  EXPECT_FALSE(IsTimeoutRunning());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that the timeout does not fire if the delay is zero.
+TEST_F(PassthroughTouchEventQueueTest, NoTouchTimeoutIfTimeoutDelayIsZero) {
+  SetUpForTimeoutTesting(base::TimeDelta(), base::TimeDelta());
+
+  // As the delay is zero, timeout behavior should be disabled.
+  PressTouchPoint(0, 1);
+  EXPECT_FALSE(IsTimeoutRunning());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that timeout delays for mobile sites take effect when appropriate.
+TEST_F(PassthroughTouchEventQueueTest, TouchTimeoutConfiguredForMobile) {
+  base::TimeDelta desktop_delay = DefaultTouchTimeoutDelay();
+  base::TimeDelta mobile_delay = base::TimeDelta();
+  SetUpForTimeoutTesting(desktop_delay, mobile_delay);
+
+  // The desktop delay is non-zero, allowing timeout behavior.
+  SetIsMobileOptimizedSite(false);
+
+  PressTouchPoint(0, 1);
+  ASSERT_TRUE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  ReleaseTouchPoint(0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+  ASSERT_FALSE(IsTimeoutRunning());
+
+  // The mobile delay is zero, preventing timeout behavior.
+  SetIsMobileOptimizedSite(true);
+
+  PressTouchPoint(0, 1);
+  EXPECT_FALSE(IsTimeoutRunning());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that a TouchCancel timeout plays nice when the timed out touch stream
+// turns into a scroll gesture sequence.
+TEST_F(PassthroughTouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The cancelled sequence may turn into a scroll gesture.
+  WebGestureEvent followup_scroll(
+      WebInputEvent::GestureScrollBegin, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+  SetFollowupEvent(followup_scroll);
+
+  // Delay the ack.
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+
+  // The timeout should have fired, disabling touch forwarding until both acks
+  // are received, acking the timed out event.
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Ack the original event, triggering a TouchCancel.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Ack the cancel event.  Normally, this would resume touch forwarding,
+  // but we're still within a scroll gesture so it remains disabled.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Try to forward touch events for the current sequence.
+  GetAndResetSentEventCount();
+  GetAndResetAckedEventCount();
+  MoveTouchPoint(0, 1, 1);
+  ReleaseTouchPoint(0);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+
+  // Now end the scroll sequence, resuming touch handling.
+  SendGestureEvent(blink::WebInputEvent::GestureScrollEnd);
+  PressTouchPoint(0, 1);
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that a TouchCancel timeout plays nice when the timed out touch stream
+// turns into a scroll gesture sequence, but the original event acks are
+// significantly delayed.
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchTimeoutWithFollowupGestureAndDelayedAck) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The cancelled sequence may turn into a scroll gesture.
+  WebGestureEvent followup_scroll(
+      WebInputEvent::GestureScrollBegin, WebInputEvent::NoModifiers,
+      ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
+  SetFollowupEvent(followup_scroll);
+
+  // Delay the ack.
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+
+  // The timeout should have fired, disabling touch forwarding until both acks
+  // are received and acking the timed out event.
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Try to forward a touch event.
+  GetAndResetSentEventCount();
+  GetAndResetAckedEventCount();
+  MoveTouchPoint(0, 1, 1);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now end the scroll sequence.  Events will not be forwarded until the two
+  // outstanding touch acks are received.
+  SendGestureEvent(blink::WebInputEvent::GestureScrollEnd);
+  MoveTouchPoint(0, 2, 2);
+  ReleaseTouchPoint(0);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+
+  // Ack the original event, triggering a cancel.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Ack the cancel event, resuming touch forwarding.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  PressTouchPoint(0, 1);
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+}
+
+// Tests that a delayed TouchEvent ack will not trigger a TouchCancel timeout if
+// the timed-out event had no consumer.
+TEST_F(PassthroughTouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
+  SetUpForTimeoutTesting();
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_TRUE(IsTimeoutRunning());
+
+  // Delay the ack.
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+
+  // The timeout should have fired, synthetically ack'ing the timed out event.
+  // TouchEvent forwarding is disabled until the original ack is received.
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Touch events should not be forwarded until we receive the original ack.
+  MoveTouchPoint(0, 1, 1);
+  ReleaseTouchPoint(0);
+  ASSERT_EQ(0U, GetAndResetSentEventCount());
+  ASSERT_EQ(2U, GetAndResetAckedEventCount());
+
+  // Ack'ing the original event should not trigger a cancel event, as the
+  // TouchStart had no consumer.  However, it should re-enable touch forwarding.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Subsequent events should be handled normally.
+  PressTouchPoint(0, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that TouchMove's movedBeyondSlopRegion is set to false if within the
+// boundary-inclusive slop region for an unconsumed TouchStart.
+TEST_F(PassthroughTouchEventQueueTest, TouchMovedBeyondSlopRegionCheck) {
+  SetUpForTouchMoveSlopTesting(kSlopLengthDips);
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove's movedBeyondSlopRegion within the slop region is set to false.
+  MoveTouchPoint(0, 0, kHalfSlopLengthDips);
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(acked_event().movedBeyondSlopRegion);
+
+  MoveTouchPoint(0, kHalfSlopLengthDips, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(acked_event().movedBeyondSlopRegion);
+
+  MoveTouchPoint(0, -kHalfSlopLengthDips, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(acked_event().movedBeyondSlopRegion);
+
+  MoveTouchPoint(0, -kSlopLengthDips, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(acked_event().movedBeyondSlopRegion);
+
+  MoveTouchPoint(0, 0, kSlopLengthDips);
+  EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(acked_event().movedBeyondSlopRegion);
+
+  // When a TouchMove exceeds the (Euclidean) distance, the TouchMove's
+  // movedBeyondSlopRegion is set to true.
+  const float kFortyFiveDegreeSlopLengthXY =
+      kSlopLengthDips * std::sqrt(2.f) / 2;
+  MoveTouchPoint(0, kFortyFiveDegreeSlopLengthXY + .2f,
+                 kFortyFiveDegreeSlopLengthXY + .2f);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_TRUE(acked_event().movedBeyondSlopRegion);
+}
+
+// Tests that even very small TouchMove's movedBeyondSlopRegion is set to true
+// when the slop region's dimension is 0.
+TEST_F(PassthroughTouchEventQueueTest,
+       MovedBeyondSlopRegionAlwaysTrueIfDimensionZero) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Small TouchMove's movedBeyondSlopRegion is set to true.
+  MoveTouchPoint(0, 0.001f, 0.001f);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_TRUE(acked_event().movedBeyondSlopRegion);
+}
+
+// Tests that secondary touch points can be forwarded even if the primary touch
+// point had no consumer.
+TEST_F(PassthroughTouchEventQueueTest,
+       SecondaryTouchForwardedAfterPrimaryHadNoConsumer) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Events should not be forwarded, as the point had no consumer.
+  MoveTouchPoint(0, 0, 15);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Simulate a secondary pointer press.
+  PressTouchPoint(20, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove with a secondary pointer should not be suppressed.
+  MoveTouchPoint(1, 25, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that secondary touch points can be forwarded after scrolling begins
+// while first touch point has no consumer.
+TEST_F(PassthroughTouchEventQueueTest,
+       NoForwardingAfterScrollWithNoTouchConsumers) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  WebGestureEvent followup_scroll(WebInputEvent::GestureScrollBegin,
+                                  WebInputEvent::NoModifiers,
+                                  WebInputEvent::TimeStampForTesting);
+  SetFollowupEvent(followup_scroll);
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // The secondary pointer press should be forwarded.
+  PressTouchPoint(20, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove with a secondary pointer should also be forwarded.
+  MoveTouchPoint(1, 25, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+TEST_F(PassthroughTouchEventQueueTest, TouchAbsorptionWithConsumedFirstMove) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 20, 5);
+  SendGestureEvent(blink::WebInputEvent::GestureScrollBegin);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+
+  // Even if the first touchmove event was consumed, subsequent unconsumed
+  // touchmove events should trigger scrolling.
+  MoveTouchPoint(0, 60, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  MoveTouchPoint(0, 20, 5);
+  WebGestureEvent followup_scroll(WebInputEvent::GestureScrollUpdate,
+                                  WebInputEvent::NoModifiers,
+                                  WebInputEvent::TimeStampForTesting);
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Touch moves are sent right away.
+  MoveTouchPoint(0, 60, 5);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+}
+
+TEST_F(PassthroughTouchEventQueueTest, TouchStartCancelableDuringScroll) {
+  // Queue a touchstart and touchmove that go unconsumed, transitioning to an
+  // active scroll sequence.
+  PressTouchPoint(0, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  SendGestureEvent(blink::WebInputEvent::GestureScrollBegin);
+  SendGestureEvent(blink::WebInputEvent::GestureScrollUpdate);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  // Even though scrolling has begun, touchstart events should be cancelable,
+  // allowing, for example, customized pinch processing.
+  PressTouchPoint(10, 11);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  // As the touch start was consumed, touchmoves should no longer be throttled.
+  MoveTouchPoint(1, 11, 11);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  // With throttling disabled, touchend and touchmove events should also be
+  // cancelable.
+  MoveTouchPoint(1, 12, 12);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ReleaseTouchPoint(1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  // If subsequent touchmoves aren't consumed, the generated scroll events
+  // will restore async touch dispatch.
+  MoveTouchPoint(0, 25, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SendGestureEvent(blink::WebInputEvent::GestureScrollUpdate);
+  EXPECT_EQ(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  AdvanceTouchTime(kMinSecondsBetweenThrottledTouchmoves + 0.1);
+  MoveTouchPoint(0, 30, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_NE(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+
+  // The touchend will be uncancelable during an active scroll sequence.
+  ReleaseTouchPoint(0);
+  EXPECT_NE(WebInputEvent::Blocking, sent_event().dispatchType);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+}
+
+TEST_F(PassthroughTouchEventQueueTest, UnseenTouchPointerIdsNotForwarded) {
+  SyntheticWebTouchEvent event;
+  event.PressPoint(0, 0);
+  SendTouchEvent(event);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a previously unseen pointer id; it should not be sent.
+  int press_id = event.touches[0].id;
+  event.MovePoint(0, 1, 1);
+  event.touches[0].id = 7;
+  SendTouchEvent(event);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a valid id; it should be sent.
+  event.touches[0].id = press_id;
+  SendTouchEvent(event);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Do the same for release.
+  event.ReleasePoint(0);
+  event.touches[0].id = 11;
+  SendTouchEvent(event);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a valid id after release it shouldn't be sent.
+  event.touches[0].id = press_id;
+  SendTouchEvent(event);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that touch points states are correct in TouchMove events.
+TEST_F(PassthroughTouchEventQueueTest, PointerStatesInTouchMove) {
+  PressTouchPoint(1, 1);
+  PressTouchPoint(2, 2);
+  PressTouchPoint(3, 3);
+  EXPECT_EQ(3U, queued_event_count());
+  EXPECT_EQ(3U, GetAndResetSentEventCount());
+  PressTouchPoint(4, 4);
+
+  // Receive ACK for the first three touch-events.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Test current touches state before sending TouchMoves.
+  const WebTouchEvent& event1 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchStart, event1.type());
+  EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[0].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[1].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[2].state);
+  EXPECT_EQ(WebTouchPoint::StatePressed, event1.touches[3].state);
+
+  // Move x-position for 1st touch, y-position for 2nd touch
+  // and do not move other touches.
+  MoveTouchPoints(0, 1.1f, 1.f, 1, 2.f, 20.001f);
+  MoveTouchPoints(2, 3.f, 3.f, 3, 4.f, 4.f);
+  EXPECT_EQ(3U, queued_event_count());
+
+  // Receive an ACK for the last TouchPress event.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // 1st TouchMove is sent. Test for touches state.
+  const WebTouchEvent& event2 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event2.type());
+  EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[0].state);
+  EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[1].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event2.touches[2].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event2.touches[3].state);
+
+  // Move only 4th touch but not others.
+  MoveTouchPoints(0, 1.1f, 1.f, 1, 2.f, 20.001f);
+  MoveTouchPoints(2, 3.f, 3.f, 3, 4.1f, 4.1f);
+
+  // Receive an ACK for previous (1st) TouchMove.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // 2nd TouchMove is sent. Test for touches state.
+  const WebTouchEvent& event3 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event3.type());
+  EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[0].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[1].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[2].state);
+  EXPECT_EQ(WebTouchPoint::StateMoved, event3.touches[3].state);
+}
+
+// Tests that touch point state is correct in TouchMove events
+// when point properties other than position changed.
+TEST_F(PassthroughTouchEventQueueTest,
+       PointerStatesWhenOtherThanPositionChanged) {
+  PressTouchPoint(1, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // Default initial radiusX/Y is (1.f, 1.f).
+  // Default initial rotationAngle is 1.f.
+  // Default initial force is 1.f.
+
+  // Change touch point radius only.
+  ChangeTouchPointRadius(0, 1.5f, 1.f);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // TouchMove is sent. Test for pointer state.
+  const WebTouchEvent& event1 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event1.type());
+  EXPECT_EQ(WebTouchPoint::StateMoved, event1.touches[0].state);
+
+  // Change touch point force.
+  ChangeTouchPointForce(0, 0.9f);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // TouchMove is sent. Test for pointer state.
+  const WebTouchEvent& event2 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event2.type());
+  EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[0].state);
+
+  // Change touch point rotationAngle.
+  ChangeTouchPointRotationAngle(0, 1.1f);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // TouchMove is sent. Test for pointer state.
+  const WebTouchEvent& event3 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event3.type());
+  EXPECT_EQ(WebTouchPoint::StateMoved, event3.touches[0].state);
+
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(4U, GetAndResetSentEventCount());
+  EXPECT_EQ(4U, GetAndResetAckedEventCount());
+}
+
+// Tests that TouchMoves are filtered when none of the points are changed.
+TEST_F(PassthroughTouchEventQueueTest, FilterTouchMovesWhenNoPointerChanged) {
+  PressTouchPoint(1, 1);
+  PressTouchPoint(2, 2);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
+
+  // Move 1st touch point.
+  MoveTouchPoint(0, 10, 10);
+  EXPECT_EQ(1U, queued_event_count());
+
+  // TouchMove should be allowed and test for touches state.
+  const WebTouchEvent& event1 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event1.type());
+  EXPECT_EQ(WebTouchPoint::StateMoved, event1.touches[0].state);
+  EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[1].state);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Do not really move any touch points, but use previous values.
+  MoveTouchPoint(0, 10, 10);
+  ChangeTouchPointRadius(1, 1, 1);
+  MoveTouchPoint(1, 2, 2);
+  EXPECT_EQ(4U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  // The TouchMove but should be filtered when none of the touch points have
+  // changed but don't get acked right away.
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Receive an ACK for 1st TouchMove.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(4U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // Move 2nd touch point.
+  MoveTouchPoint(1, 3, 3);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+
+  // TouchMove should be allowed and test for touches state.
+  const WebTouchEvent& event2 = sent_event();
+  EXPECT_EQ(WebInputEvent::TouchMove, event2.type());
+  EXPECT_EQ(WebTouchPoint::StateStationary, event2.touches[0].state);
+  EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[1].state);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that touch-scroll-notification is queued normally.
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchScrollNotificationOrder_EmptyQueue) {
+  PrependTouchScrollNotification();
+
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+}
+
+// Tests touch-scroll-notification firing is appended to the tail of sent
+// events since all events are sent right away.
+TEST_F(PassthroughTouchEventQueueTest,
+       TouchScrollNotificationOrder_EndOfQueue) {
+  PressTouchPoint(1, 1);
+
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Send the touch-scroll-notification when 3 events are in the queue.
+  PrependTouchScrollNotification();
+
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Send ACKs.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_IGNORED);
+
+  // Touch-scroll-start Ack is not reported to client.
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  EXPECT_EQ(WebInputEvent::TouchStart, all_sent_events()[0].type());
+  EXPECT_EQ(WebInputEvent::TouchScrollStarted, all_sent_events()[1].type());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+}
+
+// Tests that if touchStartOrFirstTouchMove is correctly set up for touch
+// events.
+TEST_F(PassthroughTouchEventQueueTest, TouchStartOrFirstTouchMove) {
+  PressTouchPoint(1, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type());
+  EXPECT_TRUE(sent_event().touchStartOrFirstTouchMove);
+
+  MoveTouchPoint(0, 5, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type());
+  EXPECT_TRUE(sent_event().touchStartOrFirstTouchMove);
+
+  MoveTouchPoint(0, 15, 15);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type());
+  EXPECT_FALSE(sent_event().touchStartOrFirstTouchMove);
+
+  ReleaseTouchPoint(0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchEnd, sent_event().type());
+  EXPECT_FALSE(sent_event().touchStartOrFirstTouchMove);
+}
+
+}  // namespace content
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
index 932393d..15dded6 100644
--- a/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
+++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_win.cc
@@ -202,7 +202,7 @@
   }
 
   std::vector<HANDLE> handles;
-  file_handles.reserve(file_names.size() + file_handles.size());
+  handles.reserve(file_names.size() + file_handles.size());
   for (const base::string16& file_name : file_names) {
     // This leaks the handles, since they are used as the reference key to
     // CreateStreamFromKey, and DirectWrite requires the reference keys to
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 0fdae4b..9d993b3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1196,6 +1196,7 @@
     "../browser/renderer_host/dwrite_font_proxy_message_filter_win_unittest.cc",
     "../browser/renderer_host/input/gesture_event_queue_unittest.cc",
     "../browser/renderer_host/input/input_router_impl_unittest.cc",
+    "../browser/renderer_host/input/legacy_touch_event_queue_unittest.cc",
     "../browser/renderer_host/input/mock_input_ack_handler.cc",
     "../browser/renderer_host/input/mock_input_ack_handler.h",
     "../browser/renderer_host/input/mock_input_router_client.cc",
@@ -1203,6 +1204,7 @@
     "../browser/renderer_host/input/motion_event_web_unittest.cc",
     "../browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc",
     "../browser/renderer_host/input/mouse_wheel_rails_filter_unittest_mac.cc",
+    "../browser/renderer_host/input/passthrough_touch_event_queue_unittest.cc",
     "../browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc",
     "../browser/renderer_host/input/stylus_text_selector_unittest.cc",
     "../browser/renderer_host/input/synthetic_gesture_controller_unittest.cc",
@@ -1210,7 +1212,6 @@
     "../browser/renderer_host/input/tap_suppression_controller_unittest.cc",
     "../browser/renderer_host/input/touch_action_filter_unittest.cc",
     "../browser/renderer_host/input/touch_emulator_unittest.cc",
-    "../browser/renderer_host/input/touch_event_queue_unittest.cc",
     "../browser/renderer_host/input/web_input_event_builders_android_unittest.cc",
     "../browser/renderer_host/input/web_input_event_builders_mac_unittest.mm",
     "../browser/renderer_host/input/web_input_event_util_unittest.cc",
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index fc234f7..8b8c6a9 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -196,6 +196,7 @@
               "lib/browser/headless_browser_context_options.h",
               "lib/browser/headless_browser_impl.cc",
               "lib/browser/headless_browser_impl.h",
+              "lib/browser/headless_browser_impl_mac.mm",
               "lib/browser/headless_browser_main_parts.cc",
               "lib/browser/headless_browser_main_parts.h",
               "lib/browser/headless_content_browser_client.cc",
@@ -289,7 +290,6 @@
     "//net",
     "//services/service_manager/public/cpp",
     "//third_party/mesa:osmesa",
-    "//ui/aura",
     "//ui/base",
     "//ui/compositor",
     "//ui/display",
@@ -305,6 +305,10 @@
     ]
   }
 
+  if (!is_mac) {
+    deps += [ "//ui/aura" ]
+  }
+
   if (headless_use_embedded_resources) {
     deps += [ ":embed_resources" ]
     sources += [
diff --git a/headless/lib/browser/headless_browser_impl_mac.mm b/headless/lib/browser/headless_browser_impl_mac.mm
new file mode 100644
index 0000000..e1911162
--- /dev/null
+++ b/headless/lib/browser/headless_browser_impl_mac.mm
@@ -0,0 +1,27 @@
+// 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 "headless/lib/browser/headless_browser_impl.h"
+
+#include "content/public/browser/web_contents.h"
+#include "ui/base/cocoa/base_view.h"
+
+namespace headless {
+
+void HeadlessBrowserImpl::PlatformInitialize() {}
+
+void HeadlessBrowserImpl::PlatformCreateWindow() {}
+
+void HeadlessBrowserImpl::PlatformInitializeWebContents(
+    const gfx::Size& initial_size,
+    content::WebContents* web_contents) {
+  NSView* web_view = web_contents->GetNativeView();
+  [web_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
+
+  NSRect frame = NSMakeRect(0, 0, initial_size.width(), initial_size.height());
+  [web_view setFrame:frame];
+  return;
+}
+
+}  // namespace headless
diff --git a/headless/lib/browser/headless_content_browser_client.cc b/headless/lib/browser/headless_content_browser_client.cc
index e993fc1..ade0c60f 100644
--- a/headless/lib/browser/headless_content_browser_client.cc
+++ b/headless/lib/browser/headless_content_browser_client.cc
@@ -170,6 +170,7 @@
       callback);
 }
 
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
 void HeadlessContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
     const base::CommandLine& command_line,
     int child_process_id,
@@ -180,6 +181,7 @@
     mappings->Share(kCrashDumpSignal, crash_signal_fd);
 #endif  // defined(HEADLESS_USE_BREAKPAD)
 }
+#endif  // defined(OS_POSIX) && !defined(OS_MACOSX)
 
 void HeadlessContentBrowserClient::AppendExtraCommandLineSwitches(
     base::CommandLine* command_line,
diff --git a/headless/lib/browser/headless_content_browser_client.h b/headless/lib/browser/headless_content_browser_client.h
index aa188832..53a95040 100644
--- a/headless/lib/browser/headless_content_browser_client.h
+++ b/headless/lib/browser/headless_content_browser_client.h
@@ -28,10 +28,12 @@
       content::BrowserContext* context,
       content::StoragePartition* partition,
       const storage::OptionalQuotaSettingsCallback& callback) override;
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
   void GetAdditionalMappedFilesForChildProcess(
       const base::CommandLine& command_line,
       int child_process_id,
       content::FileDescriptorInfo* mappings) override;
+#endif
   void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
                                       int child_process_id) override;
 
diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc
index 1aa61b4..72df5a2 100644
--- a/headless/lib/headless_content_main_delegate.cc
+++ b/headless/lib/headless_content_main_delegate.cc
@@ -147,6 +147,7 @@
   g_headless_crash_client.Pointer()->set_crash_dumps_dir(
       browser_->options()->crash_dumps_dir);
 
+#if !defined(OS_MACOSX)
   if (!browser_->options()->enable_crash_reporter) {
     DCHECK(!breakpad::IsCrashReporterEnabled());
     return;
@@ -155,6 +156,7 @@
   if (process_type != switches::kZygoteProcess)
     breakpad::InitCrashReporter(process_type);
 #endif  // defined(HEADLESS_USE_BREAKPAD)
+#endif  // !defined(OS_MACOSX)
 }
 
 void HeadlessContentMainDelegate::PreSandboxStartup() {
@@ -168,7 +170,9 @@
   if (command_line.HasSwitch(switches::kEnableLogging))
     InitLogging(command_line);
 #endif
+#if !defined(OS_MACOSX)
   InitCrashReporter(command_line);
+#endif
   InitializeResourceBundle();
 }
 
@@ -198,6 +202,7 @@
   return 0;
 }
 
+#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID)
 void HeadlessContentMainDelegate::ZygoteForked() {
   const base::CommandLine& command_line(
       *base::CommandLine::ForCurrentProcess());
@@ -208,6 +213,7 @@
   // bail out gracefully if the browser process hasn't enabled crash reporting.
   breakpad::InitCrashReporter(process_type);
 }
+#endif
 
 // static
 HeadlessContentMainDelegate* HeadlessContentMainDelegate::GetInstance() {
diff --git a/headless/lib/headless_content_main_delegate.h b/headless/lib/headless_content_main_delegate.h
index 35eff70c..cb33f22 100644
--- a/headless/lib/headless_content_main_delegate.h
+++ b/headless/lib/headless_content_main_delegate.h
@@ -35,11 +35,14 @@
   int RunProcess(
       const std::string& process_type,
       const content::MainFunctionParams& main_function_params) override;
-  void ZygoteForked() override;
   content::ContentBrowserClient* CreateContentBrowserClient() override;
 
   HeadlessBrowserImpl* browser() const { return browser_.get(); }
 
+#if !defined(OS_MACOSX) && defined(OS_POSIX) && !defined(OS_ANDROID)
+  void ZygoteForked() override;
+#endif
+
  private:
   friend class HeadlessBrowserTest;
 
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 37796ba42..f2107f4 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2561,3 +2561,6 @@
 # Sheriff failures 2017-02-23
 crbug.com/695352 [ Mac ] virtual/threaded/compositing/visibility/overlays-persist-on-navigation.html [ Pass Crash ]
 
+# Sheriff failures 2017-02-27
+crbug.com/696398 [ Win7 ] virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem.html [ Failure ]
+crbug.com/696407 [ Linux ] external/wpt/html/browsers/history/the-location-interface/location-protocol-setter-with-colon.sub.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
index a3dec7a..e6a549c 100644
--- a/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/worklet/webexposed/global-interface-listing-paint-worklet-expected.txt
@@ -39,7 +39,7 @@
 CONSOLE MESSAGE: line 138:     method divide
 CONSOLE MESSAGE: line 138:     method multiply
 CONSOLE MESSAGE: line 138:     method subtract
-CONSOLE MESSAGE: line 138: interface CSSMatrixTransformComponent : CSSTransformComponent
+CONSOLE MESSAGE: line 138: interface CSSMatrixComponent : CSSTransformComponent
 CONSOLE MESSAGE: line 138:     getter a
 CONSOLE MESSAGE: line 138:     getter b
 CONSOLE MESSAGE: line 138:     getter c
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixTransformComponent.html b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
similarity index 66%
rename from third_party/WebKit/LayoutTests/typedcssom/cssMatrixTransformComponent.html
rename to third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
index 731013d..bb84910 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixTransformComponent.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
@@ -4,27 +4,27 @@
 
 <script>
 var values = [
-  {input: new CSSMatrixTransformComponent(0, 0, 0, 0, 0, 0),
+  {input: new CSSMatrixComponent(0, 0, 0, 0, 0, 0),
     a: 0, b: 0, c: 0, d: 0, e: 0, f: 0,
     is2D: true, cssText: "matrix(0, 0, 0, 0, 0, 0)"},
-  {input: new CSSMatrixTransformComponent(2, 4, 6, 8, 10, 12),
+  {input: new CSSMatrixComponent(2, 4, 6, 8, 10, 12),
     a: 2, b: 4, c: 6, d: 8, e: 10, f: 12,
     is2D: true, cssText: "matrix(2, 4, 6, 8, 10, 12)"},
-  {input: new CSSMatrixTransformComponent(-2, -4, -6, -8, -10, -12),
+  {input: new CSSMatrixComponent(-2, -4, -6, -8, -10, -12),
     a: -2, b: -4, c: -6, d: -8, e: -10, f: -12,
     is2D: true, cssText: "matrix(-2, -4, -6, -8, -10, -12)"},
-  {input: new CSSMatrixTransformComponent(1.1, -2.2, 3.3, -4.4, 5.5, 0.6),
+  {input: new CSSMatrixComponent(1.1, -2.2, 3.3, -4.4, 5.5, 0.6),
     a: 1.1, b: -2.2, c: 3.3, d: -4.4, e: 5.5, f: 0.6,
     is2D: true, cssText: "matrix(1.1, -2.2, 3.3, -4.4, 5.5, 0.6)"},
-  {input: new CSSMatrixTransformComponent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+  {input: new CSSMatrixComponent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     m11: 0, m12: 0, m13: 0, m14: 0, m21: 0, m22: 0, m23: 0, m24: 0,
     m31: 0, m32: 0, m33: 0, m34: 0, m41: 0, m42: 0, m43: 0, m44: 0,
     is2D: false, cssText: "matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)"},
-  {input: new CSSMatrixTransformComponent(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44),
+  {input: new CSSMatrixComponent(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44),
     m11: 11, m12: 12, m13: 13, m14: 14, m21: 21, m22: 22, m23: 23, m24: 24,
     m31: 31, m32: 32, m33: 33, m34: 34, m41: 41, m42: 42, m43: 43, m44: 44,
     is2D: false, cssText: "matrix3d(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44)"},
-  {input: new CSSMatrixTransformComponent(1.1, 1.2, -13, -1.4, 2, 0, -2, 4, 3.1, 3, 3, 3.4, -4.1, 42, 43, 4.4),
+  {input: new CSSMatrixComponent(1.1, 1.2, -13, -1.4, 2, 0, -2, 4, 3.1, 3, 3, 3.4, -4.1, 42, 43, 4.4),
     m11: 1.1, m12: 1.2, m13: -13, m14: -1.4, m21: 2, m22: 0, m23: -2, m24: 4,
     m31: 3.1, m32: 3, m33: 3, m34: 3.4, m41: -4.1, m42: 42, m43: 43, m44: 4.4,
     is2D: false, cssText: "matrix3d(1.1, 1.2, -13, -1.4, 2, 0, -2, 4, 3.1, 3, 3, 3.4, -4.1, 42, 43, 4.4)"}
@@ -42,27 +42,27 @@
       assert_equals(values[i].input[attribute], values[i][attribute]);
     }
   }
-}, "Test that the (a, ... , f) and (m11, ... , m44) attributes for CSSMatrixTransformComponent are correct.");
+}, "Test that the (a, ... , f) and (m11, ... , m44) attributes for CSSMatrixComponent are correct.");
 
 test(function() {
   for (var i = 0; i < values.length; ++i) {
     assert_equals(values[i].input.is2D(), values[i].is2D);
   }
-}, "Test that the is2D values for CSSMatrixTransformComponent are correct.");
+}, "Test that the is2D values for CSSMatrixComponent are correct.");
 
 test(function() {
   for (var i = 0; i < values.length; ++i) {
     assert_equals(values[i].input.cssText, values[i].cssText);
   }
-}, "Test that the cssText for CSSMatrixTransformComponent is correct.");
+}, "Test that the cssText for CSSMatrixComponent is correct.");
 
 test(function() {
-  assert_throws(null, function() { new CSSMatrixTransformComponent(); });
-  assert_throws(null, function() { new CSSMatrixTransformComponent(0); });
-  assert_throws(null, function() { new CSSMatrixTransformComponent(0, 1, 2, 3, 4); });
-  assert_throws(null, function() { new CSSMatrixTransformComponent(0, 1, 2, 3, 4, 5, 6); });
-  assert_throws(null, function() { new CSSMatrixTransformComponent(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); });
-}, "Test that invalid number of arguments for CSSMatrixTransformComponent throws an exception.");
+  assert_throws(null, function() { new CSSMatrixComponent(); });
+  assert_throws(null, function() { new CSSMatrixComponent(0); });
+  assert_throws(null, function() { new CSSMatrixComponent(0, 1, 2, 3, 4); });
+  assert_throws(null, function() { new CSSMatrixComponent(0, 1, 2, 3, 4, 5, 6); });
+  assert_throws(null, function() { new CSSMatrixComponent(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); });
+}, "Test that invalid number of arguments for CSSMatrixComponent throws an exception.");
 
 test(function() {
   var attributeValues = attributeValues2D.concat(attributeValues3D);
@@ -75,6 +75,6 @@
     assert_equals(inputAsMatrix.is2D(), values[i].input.is2D());
     assert_equals(inputAsMatrix.cssText, values[i].input.cssText);
   }
-}, "Test that asMatrix has all the same properties as the original CSSMatrixTransformComponent.");
+}, "Test that asMatrix has all the same properties as the original CSSMatrixComponent.");
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html b/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
index a00655b..93fc095 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssRotation.html
@@ -10,33 +10,33 @@
 var values = [
   {input: new CSSRotation(angle(0)), angle: 0, x: 0, y: 0, z: 1,
     is2D: true, cssText: "rotate(0deg)",
-    asMatrix: new CSSMatrixTransformComponent(1, 0, 0, 1, 0, 0)},
+    asMatrix: new CSSMatrixComponent(1, 0, 0, 1, 0, 0)},
   {input: new CSSRotation(angle(10)), angle: 10, x: 0, y: 0, z: 1,
     is2D: true, cssText: "rotate(10deg)",
-    asMatrix: new CSSMatrixTransformComponent(0.9848077, 0.1736481, -0.1736481, 0.9848077, 0, 0)},
+    asMatrix: new CSSMatrixComponent(0.9848077, 0.1736481, -0.1736481, 0.9848077, 0, 0)},
   {input: new CSSRotation(angle(-21)), angle: -21, x: 0, y: 0, z: 1,
     is2D: true, cssText: "rotate(-21deg)",
-    asMatrix: new CSSMatrixTransformComponent(0.9335804, -0.3583679, 0.3583679, 0.9335804, 0, 0)},
+    asMatrix: new CSSMatrixComponent(0.9335804, -0.3583679, 0.3583679, 0.9335804, 0, 0)},
   {input: new CSSRotation(angle(3.2)), angle: 3.2, x: 0, y: 0, z: 1,
     is2D: true, cssText: "rotate(3.2deg)",
-    asMatrix: new CSSMatrixTransformComponent(0.9984407, 0.0558215, -0.0558215, 0.9984407, 0, 0)},
+    asMatrix: new CSSMatrixComponent(0.9984407, 0.0558215, -0.0558215, 0.9984407, 0, 0)},
   {input: new CSSRotation(0, 0, 1, angle(90)), angle: 90, x: 0, y: 0, z: 1,
     is2D: false, cssText: "rotate3d(0, 0, 1, 90deg)",
-    asMatrix: new CSSMatrixTransformComponent(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)},
+    asMatrix: new CSSMatrixComponent(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)},
   {input: new CSSRotation(2.7, -3, 4.4, angle(0)), angle: 0, x: 2.7, y: -3, z: 4.4,
     is2D: false, cssText: "rotate3d(2.7, -3, 4.4, 0deg)",
-    asMatrix: new CSSMatrixTransformComponent(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)},
+    asMatrix: new CSSMatrixComponent(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)},
   {input: new CSSRotation(2, 3, 4, angle(10)), angle: 10, x: 2, y: 3, z: 4,
     is2D: false, cssText: "rotate3d(2, 3, 4, 10deg)",
-    asMatrix: new CSSMatrixTransformComponent(0.9869032, 0.1321258, -0.0925460, 0, -0.1258394,
+    asMatrix: new CSSMatrixComponent(0.9869032, 0.1321258, -0.0925460, 0, -0.1258394,
       0.9895225, 0.0707777, 0, 0.1009279, -0.0582048, 0.9931896, 0, 0, 0, 0, 1)},
   {input: new CSSRotation(2, 3.7, -4, angle(-1.2)), angle: -1.2, x: 2, y: 3.7, z: -4,
     is2D: false, cssText: "rotate3d(2, 3.7, -4, -1.2deg)",
-    asMatrix: new CSSMatrixTransformComponent(0.9998067, 0.01448049, 0.0132978, 0, -0.0143841,
+    asMatrix: new CSSMatrixComponent(0.9998067, 0.01448049, 0.0132978, 0, -0.0143841,
     0.9998698, -0.0073125, 0, -0.0134019, 0.0071198, 0.9998848, 0, 0, 0, 0, 1)},
   {input: new CSSRotation(1, 0, 0, new CSSAngleValue(0.5, 'turn')), angle: 180, x: 1, y: 0, z: 0,
     is2D: false, cssText: "rotate3d(1, 0, 0, 0.5turn)",
-    asMatrix: new CSSMatrixTransformComponent(1, 0, 0, 0, 0, -1, 1.2246467991473532e-16, 0, 0,
+    asMatrix: new CSSMatrixComponent(1, 0, 0, 0, 0, -1, 1.2246467991473532e-16, 0, 0,
     -1.2246467991473532e-16, -1, 0, 0, 0, 0, 1)}
 ];
 
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
index e2d5b17..30ff36b 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
@@ -79,8 +79,8 @@
     var inputAsMatrix = input.asMatrix();
     assert_equals(inputAsMatrix.is2D(), input.is2D());
 
-    var expectedMatrix = input.is2D() ? new CSSMatrixTransformComponent(input.x, 0, 0, input.y, 0, 0) :
-        new CSSMatrixTransformComponent(input.x, 0, 0, 0, 0, input.y, 0, 0, 0, 0, input.z, 0, 0, 0, 0, 1);
+    var expectedMatrix = input.is2D() ? new CSSMatrixComponent(input.x, 0, 0, input.y, 0, 0) :
+        new CSSMatrixComponent(input.x, 0, 0, 0, 0, input.y, 0, 0, 0, 0, input.z, 0, 0, 0, 0, 1);
     for (var attribute in expectedMatrix) {
       if (typeof expectedMatrix[attribute] === "number") {
         assert_approx_equals(inputAsMatrix[attribute], expectedMatrix[attribute], EPSILON);
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html b/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html
index b19e00f..f41de0c 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssSkew.html
@@ -55,7 +55,7 @@
     assert_true(inputAsMatrix.is2D());
     var tanAx = tanDegrees(input.ax.degrees);
     var tanAy = tanDegrees(input.ay.degrees);
-    var expectedMatrix = new CSSMatrixTransformComponent(1, tanAy, tanAx, 1, 0, 0);
+    var expectedMatrix = new CSSMatrixComponent(1, tanAy, tanAx, 1, 0, 0);
     for (var attribute in expectedMatrix) {
       if (typeof expectedMatrix[attribute] === "number") {
         assert_approx_equals(inputAsMatrix[attribute], expectedMatrix[attribute], EPSILON);
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssTransformValue.html b/third_party/WebKit/LayoutTests/typedcssom/cssTransformValue.html
index affeaf1..fec9fad0 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssTransformValue.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssTransformValue.html
@@ -12,21 +12,21 @@
 
 test(function() {
   var transformArray = [new CSSScale(2,2),
-    new CSSMatrixTransformComponent(1,1,1,1,1,1), new CSSScale(5,6)];
+    new CSSMatrixComponent(1,1,1,1,1,1), new CSSScale(5,6)];
   var transformValue = new CSSTransformValue(transformArray);
   assert_true(transformValue.is2D());
 }, "is2D returns true for transformValues containing only 2D transformComponents");
 
 test(function() {
   var transformArray = [new CSSScale(2,2),
-    new CSSMatrixTransformComponent(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), new CSSScale(5,6)];
+    new CSSMatrixComponent(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), new CSSScale(5,6)];
   var transformValue = new CSSTransformValue(transformArray);
   assert_false(transformValue.is2D());
 }, "is2D returns false for transformValues containing both 2D and 3D transformComponents");
 
 test(function() {
   var transformArray = [new CSSScale(2,2),
-    new CSSMatrixTransformComponent(1,1,1,1,1,1), new CSSScale(5,6)];
+    new CSSMatrixComponent(1,1,1,1,1,1), new CSSScale(5,6)];
   var transformValue = new CSSTransformValue(transformArray);
 
   var newTransformArray = [];
@@ -35,7 +35,7 @@
   }
   assert_true(newTransformArray.length == 3);
   assert_true(newTransformArray[0] instanceof CSSScale);
-  assert_true(newTransformArray[1] instanceof CSSMatrixTransformComponent);
+  assert_true(newTransformArray[1] instanceof CSSMatrixComponent);
   assert_true(newTransformArray[2] instanceof CSSScale);
 }, "CSSTransformValue can iterate through all its all its transformComponent members");
 
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index a2db1a12..ccea9da 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -527,7 +527,7 @@
     method divide
     method multiply
     method subtract
-interface CSSMatrixTransformComponent : CSSTransformComponent
+interface CSSMatrixComponent : CSSTransformComponent
     attribute @@toStringTag
     getter a
     getter b
diff --git a/third_party/WebKit/ManualTests/animate-duration.html b/third_party/WebKit/ManualTests/animate-duration.html
index b155c70..9b3c823 100644
--- a/third_party/WebKit/ManualTests/animate-duration.html
+++ b/third_party/WebKit/ManualTests/animate-duration.html
@@ -22,22 +22,22 @@
      margin: 10px;
      background-color: blue;
      z-index: 0;
-     -webkit-animation-name: slide;
-     -webkit-animation-duration: 1s;
-     -webkit-animation-direction: alternate;
-     -webkit-animation-timing-function: ease-in-out;
-     -webkit-animation-iteration-count: infinite;
+     animation-name: slide;
+     animation-duration: 1s;
+     animation-direction: alternate;
+     animation-timing-function: ease-in-out;
+     animation-iteration-count: infinite;
    }  
 
    .one {
-     -webkit-animation-duration: 0s;
+     animation-duration: 0s;
    }
 
    .two {
-     -webkit-animation-duration: 0;
+     animation-duration: 0;
    }
    
-   @-webkit-keyframes slide {
+   @keyframes slide {
      from { transform: translateX(0);      }
      to   { transform: translateX(280px);  }
    }
diff --git a/third_party/WebKit/ManualTests/animate-left.html b/third_party/WebKit/ManualTests/animate-left.html
index 3ced2ec..360b2808 100644
--- a/third_party/WebKit/ManualTests/animate-left.html
+++ b/third_party/WebKit/ManualTests/animate-left.html
@@ -12,14 +12,14 @@
              height: 200px;
              width: 200px;
              background-color: #9bb;
-             -webkit-animation-name: bounce;
-             -webkit-animation-duration: 2s;
-             -webkit-animation-iteration-count: infinite;
-             -webkit-animation-direction: alternate;
-             -webkit-animation-timing-function: ease-in-out;
+             animation-name: bounce;
+             animation-duration: 2s;
+             animation-iteration-count: infinite;
+             animation-direction: alternate;
+             animation-timing-function: ease-in-out;
          }
          
-         @-webkit-keyframes bounce {
+         @keyframes bounce {
            from {
              left: 100px;
            }
diff --git a/third_party/WebKit/ManualTests/animation-with-transition.html b/third_party/WebKit/ManualTests/animation-with-transition.html
index 37a75898..eaff5ed 100644
--- a/third_party/WebKit/ManualTests/animation-with-transition.html
+++ b/third_party/WebKit/ManualTests/animation-with-transition.html
@@ -10,17 +10,17 @@
       width: 200px;
       height: 200px;
       background-color: #696;
-      -webkit-transition: left 5s, top 5s;
+      transition: left 5s, top 5s;
     }
     
     .animate {
-      -webkit-animation-name: simple;
-      -webkit-animation-duration: 2s;
-      -webkit-animation-timing-function: linear;
-      -webkit-animation-fill-mode: backwards;
+      animation-name: simple;
+      animation-duration: 2s;
+      animation-timing-function: linear;
+      animation-fill-mode: backwards;
     }
     
-    @-webkit-keyframes simple {
+    @keyframes simple {
       50% {
         left: 300px;
       }
diff --git a/third_party/WebKit/ManualTests/animation/animation-direction-reverse-timing-functions-hardware.html b/third_party/WebKit/ManualTests/animation/animation-direction-reverse-timing-functions-hardware.html
index db8573e..9217fcf 100644
--- a/third_party/WebKit/ManualTests/animation/animation-direction-reverse-timing-functions-hardware.html
+++ b/third_party/WebKit/ManualTests/animation/animation-direction-reverse-timing-functions-hardware.html
@@ -16,30 +16,30 @@
     .animated {
       background-color: green;
       /* This timing function has value 0 at inputs of 1/3 and 2/3. Chosen because it's very asymmetric about x = y. */
-      -webkit-animation: move 3000000s 2 cubic-bezier(0.333333, 0.333333, 0.666666, -0.833333) forwards;
+      animation: move 3000000s 2 cubic-bezier(0.333333, 0.333333, 0.666666, -0.833333) forwards;
       margin-bottom: 4px;
     }
 
     .delay_zero {
-      -webkit-animation-delay: 0s;
+      animation-delay: 0s;
     }
     .delay_one_third {
-      -webkit-animation-delay: -1000000s;
+      animation-delay: -1000000s;
     }
     .delay_two_thirds {
-      -webkit-animation-delay: -2000000s;
+      animation-delay: -2000000s;
     }
     .delay_three_thirds {
-      -webkit-animation-delay: -3000000s;
+      animation-delay: -3000000s;
     }
     .delay_four_thirds {
-      -webkit-animation-delay: -4000000s;
+      animation-delay: -4000000s;
     }
     .delay_five_thirds {
-      -webkit-animation-delay: -5000000s;
+      animation-delay: -5000000s;
     }
     .delay_six_thirds {
-      -webkit-animation-delay: -6000000s;
+      animation-delay: -6000000s;
     }
 
     .expected {
@@ -59,19 +59,19 @@
     }
 
     #normal div {
-      -webkit-animation-direction: normal;
+      animation-direction: normal;
     }
     #alternate div {
-      -webkit-animation-direction: alternate;
+      animation-direction: alternate;
     }
     #reverse div {
-      -webkit-animation-direction: reverse;
+      animation-direction: reverse;
     }
     #alternate-reverse div {
-      -webkit-animation-direction: alternate-reverse;
+      animation-direction: alternate-reverse;
     }
     
-    @-webkit-keyframes move {
+    @keyframes move {
       from { transform: translateX(100px); }
       to   { transform: translateX(200px); }
     }
diff --git a/third_party/WebKit/ManualTests/animation/compositor-animation-delay.html b/third_party/WebKit/ManualTests/animation/compositor-animation-delay.html
index 089880a..def757ad 100644
--- a/third_party/WebKit/ManualTests/animation/compositor-animation-delay.html
+++ b/third_party/WebKit/ManualTests/animation/compositor-animation-delay.html
@@ -6,39 +6,39 @@
     width: 100px;
     background: blue;
     transform: translateZ(0);
-    -webkit-animation-direction: alternate;
-    -webkit-animation-duration: 2s;
-    -webkit-animation-timing-function: linear;
-    -webkit-animation-iteration-count: 6;
+    animation-direction: alternate;
+    animation-duration: 2s;
+    animation-timing-function: linear;
+    animation-iteration-count: 6;
 }
 
 .test0 {
-    -webkit-animation-delay: -30s;
+    animation-delay: -30s;
 }
 
 .test1 {
-    -webkit-animation-delay: -3s;
+    animation-delay: -3s;
 }
 
 .test2 {
-    -webkit-animation-delay: 0s;
+    animation-delay: 0s;
 }
 
 .test3 {
-    -webkit-animation-delay: 3s;
+    animation-delay: 3s;
 }
 
 .anim-left {
-    -webkit-animation-name: anim-left;
+    animation-name: anim-left;
     z-index: 100;
 }
 
 .anim-transform {
-    -webkit-animation-name: anim-transform;
+    animation-name: anim-transform;
     z-index: 200;
 }
 
-@-webkit-keyframes anim-left {
+@keyframes anim-left {
     0% {
         left: 0px;
     }
@@ -47,7 +47,7 @@
     }
 }
 
-@-webkit-keyframes anim-transform {
+@keyframes anim-transform {
     0% {
         transform: translateX(0px);
     }
diff --git a/third_party/WebKit/ManualTests/animation/compositor-animation-direction.html b/third_party/WebKit/ManualTests/animation/compositor-animation-direction.html
index ac3ab7e..14fadfb 100644
--- a/third_party/WebKit/ManualTests/animation/compositor-animation-direction.html
+++ b/third_party/WebKit/ManualTests/animation/compositor-animation-direction.html
@@ -6,39 +6,39 @@
     width: 100px;
     background: blue;
     transform: translateZ(0);
-    -webkit-animation-duration: 2s;
-    -webkit-animation-timing-function: linear;
-    -webkit-animation-fill-mode: both;
-    -webkit-animation-iteration-count: 2;
+    animation-duration: 2s;
+    animation-timing-function: linear;
+    animation-fill-mode: both;
+    animation-iteration-count: 2;
 }
 
 .normal {
-    -webkit-animation-direction: normal;
+    animation-direction: normal;
 }
 
 .reverse {
-     -webkit-animation-direction: reverse;
+     animation-direction: reverse;
 }
 
 .alternate {
-     -webkit-animation-direction: alternate;
+     animation-direction: alternate;
 }
 
 .alternate-reverse {
-     -webkit-animation-direction: alternate-reverse;
+     animation-direction: alternate-reverse;
 }
 
 .anim-left {
-    -webkit-animation-name: anim-left;
+    animation-name: anim-left;
     z-index: 100;
 }
 
 .anim-transform {
-    -webkit-animation-name: anim-transform;
+    animation-name: anim-transform;
     z-index: 200;
 }
 
-@-webkit-keyframes anim-left {
+@keyframes anim-left {
     0% {
         left: 0px;
     }
@@ -47,7 +47,7 @@
     }
 }
 
-@-webkit-keyframes anim-transform {
+@keyframes anim-transform {
     0% {
         transform: translateX(0px);
     }
diff --git a/third_party/WebKit/ManualTests/animation/compositor-animation-fill.html b/third_party/WebKit/ManualTests/animation/compositor-animation-fill.html
index 9538ffa..cd2fdfef 100644
--- a/third_party/WebKit/ManualTests/animation/compositor-animation-fill.html
+++ b/third_party/WebKit/ManualTests/animation/compositor-animation-fill.html
@@ -6,40 +6,40 @@
     width: 100px;
     background: blue;
     transform: translateZ(0);
-    -webkit-animation-direction: alternate;
-    -webkit-animation-duration: 2s;
-    -webkit-animation-timing-function: linear;
-    -webkit-animation-iteration-count: 1;
-    -webkit-animation-delay: 1s;
+    animation-direction: alternate;
+    animation-duration: 2s;
+    animation-timing-function: linear;
+    animation-iteration-count: 1;
+    animation-delay: 1s;
 }
 
 .fill-both {
-    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
 }
 
 .fill-none {
-    -webkit-animation-fill-mode: none;
+    animation-fill-mode: none;
 }
 
 .fill-forwards {
-    -webkit-animation-fill-mode: forwards;
+    animation-fill-mode: forwards;
 }
 
 .fill-backwards {
-    -webkit-animation-fill-mode: backwards;
+    animation-fill-mode: backwards;
 }
 
 .anim-left {
-    -webkit-animation-name: anim-left;
+    animation-name: anim-left;
     z-index: 100;
 }
 
 .anim-transform {
-    -webkit-animation-name: anim-transform;
+    animation-name: anim-transform;
     z-index: 200;
 }
 
-@-webkit-keyframes anim-left {
+@keyframes anim-left {
     0% {
         left: 250px;
     }
@@ -48,7 +48,7 @@
     }
 }
 
-@-webkit-keyframes anim-transform {
+@keyframes anim-transform {
     0% {
         transform: translateX(250px);
     }
diff --git a/third_party/WebKit/ManualTests/animation/compositor-animation-iteration-count.html b/third_party/WebKit/ManualTests/animation/compositor-animation-iteration-count.html
index a6659c2..0393eca8 100644
--- a/third_party/WebKit/ManualTests/animation/compositor-animation-iteration-count.html
+++ b/third_party/WebKit/ManualTests/animation/compositor-animation-iteration-count.html
@@ -6,47 +6,47 @@
     width: 100px;
     background: blue;
     transform: translateZ(0);
-    -webkit-animation-direction: alternate;
-    -webkit-animation-duration: 2s;
-    -webkit-animation-timing-function: linear;
-    -webkit-animation-fill-mode: both;
+    animation-direction: alternate;
+    animation-duration: 2s;
+    animation-timing-function: linear;
+    animation-fill-mode: both;
 }
 
 .test0 {
-    -webkit-animation-iteration-count: 0;
+    animation-iteration-count: 0;
 }
 
 .test1 {
-     -webkit-animation-iteration-count: 0.5;
+     animation-iteration-count: 0.5;
 }
 
 .test2 {
-    -webkit-animation-iteration-count: 1;
+    animation-iteration-count: 1;
 }
 
 .test3 {
-    -webkit-animation-iteration-count: 1.5;
+    animation-iteration-count: 1.5;
 }
 
 .test4 {
-    -webkit-animation-iteration-count: 2;
+    animation-iteration-count: 2;
 }
 
 .test5 {
-    -webkit-animation-iteration-count: 2.5;
+    animation-iteration-count: 2.5;
 }
 
 .anim-left {
-    -webkit-animation-name: anim-left;
+    animation-name: anim-left;
     z-index: 100;
 }
 
 .anim-transform {
-    -webkit-animation-name: anim-transform;
+    animation-name: anim-transform;
     z-index: 200;
 }
 
-@-webkit-keyframes anim-left {
+@keyframes anim-left {
     0% {
         left: 0px;
     }
@@ -55,7 +55,7 @@
     }
 }
 
-@-webkit-keyframes anim-transform {
+@keyframes anim-transform {
     0% {
         transform: translateX(0px);
     }
diff --git a/third_party/WebKit/ManualTests/animation/compositor-neutral-keyframes.html b/third_party/WebKit/ManualTests/animation/compositor-neutral-keyframes.html
index ec44af1..fb725441 100644
--- a/third_party/WebKit/ManualTests/animation/compositor-neutral-keyframes.html
+++ b/third_party/WebKit/ManualTests/animation/compositor-neutral-keyframes.html
@@ -7,26 +7,26 @@
     background: blue;
     will-change: transform;
     left: 0px;
-    -webkit-animation-duration: 2s;
-    -webkit-animation-timing-function: linear;
-    -webkit-animation-iteration-count: 2;
+    animation-duration: 2s;
+    animation-timing-function: linear;
+    animation-iteration-count: 2;
 }
 
 .anim-left {
-    -webkit-animation-name: anim-left;
+    animation-name: anim-left;
     z-index: 100;
 }
 
 .anim-transform {
-    -webkit-animation-name: anim-transform;
+    animation-name: anim-transform;
     z-index: 200;
 }
 
-@-webkit-keyframes anim-left {
+@keyframes anim-left {
     100% { left: 300px; }
 }
 
-@-webkit-keyframes anim-transform {
+@keyframes anim-transform {
     100% { transform: translateX(300px); }
 }
 </style>
diff --git a/third_party/WebKit/ManualTests/animation/transition-during-transition-with-load.html b/third_party/WebKit/ManualTests/animation/transition-during-transition-with-load.html
index 8994e803..56f008a 100644
--- a/third_party/WebKit/ManualTests/animation/transition-during-transition-with-load.html
+++ b/third_party/WebKit/ManualTests/animation/transition-during-transition-with-load.html
@@ -6,10 +6,10 @@
     width: 100px;
     background: blue;
     left: 0px;
-    -webkit-transform: translate3d(0px, 0px, 0px);
-    -webkit-transition-duration: 1s;
-    -webkit-transition-timing-function: linear;
-    -webkit-transition-property: -webkit-transform;
+    transform: translate3d(0px, 0px, 0px);
+    transition-duration: 1s;
+    transition-timing-function: linear;
+    transition-property: transform;
   }
 </style>
 <body>
@@ -29,7 +29,7 @@
     var starTime;
 
     function transitionAgain() {
-      box.style.webkitTransform = "translate3d(200px, 0px, 0px)";
+      box.style.transform = "translate3d(200px, 0px, 0px)";
     }
 
     var animate = function() {
@@ -47,7 +47,7 @@
     requestAnimationFrame(animate);
 
     startTime = window.performance.now();
-    box.style.webkitTransform = "translate3d(100px, 0px, 0px)";
+    box.style.transform = "translate3d(100px, 0px, 0px)";
   }
 </script>
 
diff --git a/third_party/WebKit/ManualTests/animation/transition-on-and-offscreen-animation.html b/third_party/WebKit/ManualTests/animation/transition-on-and-offscreen-animation.html
index e41f0fe..28d0db9 100644
--- a/third_party/WebKit/ManualTests/animation/transition-on-and-offscreen-animation.html
+++ b/third_party/WebKit/ManualTests/animation/transition-on-and-offscreen-animation.html
@@ -12,7 +12,7 @@
       width: 200px;
       border: 1px solid black;
       background-color : red;
-      -webkit-transition: -webkit-transform 3s;
+      transition: transform 3s;
     }
     
   </style>
@@ -22,19 +22,19 @@
       var box = document.getElementById("box");
 
       window.setTimeout(function() {
-        box.style.webkitTransform = "translateX(" + window.innerWidth * 2.5 + "px)";
+        box.style.transform = "translateX(" + window.innerWidth * 2.5 + "px)";
       }, 3000);
       
       window.setTimeout(function() {
-        box.style.webkitTransform = "translateX(0px)";
+        box.style.transform = "translateX(0px)";
       }, 6000);
 
       window.setTimeout(function() {
-        box.style.webkitTransform = "translateX(" + window.innerWidth * 2.5 + "px)";
+        box.style.transform = "translateX(" + window.innerWidth * 2.5 + "px)";
       }, 9000);
 
       window.setTimeout(function() {
-        box.style.webkitTransform = "translateX(0px)";
+        box.style.transform = "translateX(0px)";
       }, 12000);
     }
     window.addEventListener('load', runTest, false)
diff --git a/third_party/WebKit/ManualTests/animation/transitions-and-paused-animations.html b/third_party/WebKit/ManualTests/animation/transitions-and-paused-animations.html
index a1e2e53b7..34a623d 100644
--- a/third_party/WebKit/ManualTests/animation/transitions-and-paused-animations.html
+++ b/third_party/WebKit/ManualTests/animation/transitions-and-paused-animations.html
@@ -11,7 +11,7 @@
       height: 200px;
       width: 200px;
       border: 1px solid black;
-      -webkit-transition: -webkit-transform 0.5s;
+      transition: transform 0.5s;
     }
     
     .moved {
@@ -25,14 +25,14 @@
       margin: 50px;
       background-color: blue;
       transform: translateZ(0);
-      -webkit-animation: fade 1s infinite linear alternate;
+      animation: fade 1s infinite linear alternate;
     }
     
     .moved .box {
-      -webkit-animation-play-state: paused;
+      animation-play-state: paused;
     }
     
-    @-webkit-keyframes fade {
+    @keyframes fade {
       from { transform: rotate(-20deg); }
       to   { transform: rotate(20deg);   }
     }
diff --git a/third_party/WebKit/ManualTests/compositing/requires-backing-change.html b/third_party/WebKit/ManualTests/compositing/requires-backing-change.html
index 94ba59e0..5bb9a4a 100644
--- a/third_party/WebKit/ManualTests/compositing/requires-backing-change.html
+++ b/third_party/WebKit/ManualTests/compositing/requires-backing-change.html
@@ -7,7 +7,7 @@
       height: 100px;
       width: 100px;
       margin: 10px 0;
-      -webkit-perspective: 1000px;
+      perspective: 1000px;
     }
     
     #container.transformed, #container2.transformed {
diff --git a/third_party/WebKit/ManualTests/image-resampling-with-scale.html b/third_party/WebKit/ManualTests/image-resampling-with-scale.html
index 35a1a8cb..718724a 100644
--- a/third_party/WebKit/ManualTests/image-resampling-with-scale.html
+++ b/third_party/WebKit/ManualTests/image-resampling-with-scale.html
@@ -16,8 +16,8 @@
     image2.setAttribute('src', imgSrc);
     intervalId = setInterval(function() {
         scale += 0.001;
-        image1.style.webkitTransform = 'scale(' + scale + ')';
-        layer2.style.webkitTransform = 'scale(' + scale + ')';
+        image1.style.transform = 'scale(' + scale + ')';
+        layer2.style.transform = 'scale(' + scale + ')';
         if (scale > 1.5)
             window.clearInterval(intervalId);
     }, 10);
diff --git a/third_party/WebKit/ManualTests/interrupted-compound-transform.html b/third_party/WebKit/ManualTests/interrupted-compound-transform.html
index dbd5646c..587e44e 100644
--- a/third_party/WebKit/ManualTests/interrupted-compound-transform.html
+++ b/third_party/WebKit/ManualTests/interrupted-compound-transform.html
@@ -11,14 +11,14 @@
       height: 400px;
       margin: 20px;
       border: 1px solid black;
-      perspective: 800;
+      perspective: 800px;
     }
     #tester {
       width: 300px;
       height: 300px;
       margin: 50px;
       background-color: blue;
-      -webkit-transition: transform 3s linear;
+      transition: transform 3s linear;
       transform: rotateX(65deg) translateZ(75px) rotateZ(0deg);
       transform-style: preserve-3d;
     }
diff --git a/third_party/WebKit/ManualTests/svg-css-animate-compound.html b/third_party/WebKit/ManualTests/svg-css-animate-compound.html
index 254e541..63520e5 100644
--- a/third_party/WebKit/ManualTests/svg-css-animate-compound.html
+++ b/third_party/WebKit/ManualTests/svg-css-animate-compound.html
@@ -31,7 +31,7 @@
     height: 60px;
     width: 60px;
     border: 1px dotted black;
-    -webkit-transform-origin: top left; /* to match SVG */
+    transform-origin: top left; /* to match SVG */
   }
 
   .final {
@@ -39,14 +39,14 @@
   }
   
   #target, #ref {
-    -webkit-animation-name: bounce;
-    -webkit-animation-duration: 2s;
-    -webkit-animation-iteration-count: infinite;
-    -webkit-animation-direction: alternate;
-    -webkit-animation-timing-function: ease-in-out;
+    animation-name: bounce;
+    animation-duration: 2s;
+    animation-iteration-count: infinite;
+    animation-direction: alternate;
+    animation-timing-function: ease-in-out;
   }
        
-  @-webkit-keyframes bounce {
+  @keyframes bounce {
     from {
       transform: translate(0px, 0px) scale(1) rotate(0deg);
     }
@@ -57,7 +57,7 @@
 </style>
 </head>
 <body>
-  <h1>CSS Animation of 'webkit-transform:' property for SVG</h1>
+  <h1>CSS Animation of 'transform:' property for SVG</h1>
 
   <p>The SVG animation should be identical with the CSS one</p>
 
diff --git a/third_party/WebKit/ManualTests/svg-css-transition-compound.html b/third_party/WebKit/ManualTests/svg-css-transition-compound.html
index 0cd9069..15f46d98 100644
--- a/third_party/WebKit/ManualTests/svg-css-transition-compound.html
+++ b/third_party/WebKit/ManualTests/svg-css-transition-compound.html
@@ -32,7 +32,7 @@
     height: 60px;
     width: 60px;
     border: 1px dotted black;
-    -webkit-transform-origin: top left; /* to match SVG */
+    transform-origin: top left; /* to match SVG */
   }
 
   .final {
@@ -40,8 +40,8 @@
   }
   
   #target, #ref {
-    -webkit-transition-property: -webkit-transform;
-    -webkit-transition-duration: 1s;
+    transition-property: transform;
+    transition-duration: 1s;
   }   
 </style>
 
@@ -53,19 +53,19 @@
     var divElm = document.getElementById("ref");
 
     if (flag) {
-      svgElm.style.webkitTransform = "translate(75px, 25px) scale(2) rotate(45deg)";
-      divElm.style.webkitTransform = "translate(75px, 25px) scale(2) rotate(45deg)";
+      svgElm.style.transform = "translate(75px, 25px) scale(2) rotate(45deg)";
+      divElm.style.transform = "translate(75px, 25px) scale(2) rotate(45deg)";
     }
     else {
-      svgElm.style.webkitTransform = "translate(0px, 0px) scale(1) rotate(0deg)";
-      divElm.style.webkitTransform = "translate(0px, 0px) scale(1) rotate(0deg)";
+      svgElm.style.transform = "translate(0px, 0px) scale(1) rotate(0deg)";
+      divElm.style.transform = "translate(0px, 0px) scale(1) rotate(0deg)";
     }
     flag = !flag;
   }
 </script>
 </head>
 <body>
-  <h1>CSS Transition of "-webkit-trasform" property for SVG elements</h1>
+  <h1>CSS Transition of "trasform" property for SVG elements</h1>
 
   <p>The element below should transition when button is clicked</p> 
   <p>The SVG transition should be identical with the CSS one</p>
diff --git a/third_party/WebKit/ManualTests/transition-events.html b/third_party/WebKit/ManualTests/transition-events.html
index 36cf940..4c2ef172 100644
--- a/third_party/WebKit/ManualTests/transition-events.html
+++ b/third_party/WebKit/ManualTests/transition-events.html
@@ -13,8 +13,8 @@
       margin: 10px;
       background-color: blue;
       z-index: 0;
-      -webkit-transition-property: left;
-      -webkit-transition-duration: 2s;
+      transition-property: left;
+      transition-duration: 2s;
       left: 0px;
     }
 
@@ -25,8 +25,8 @@
       margin: 10px;
       background-color: red;
       z-index: 0;
-      -webkit-transition-property: left;
-      -webkit-transition-duration: 2s;
+      transition-property: left;
+      transition-duration: 2s;
       left: 0px;
     }
 
@@ -43,7 +43,7 @@
     var switch1 = true;
     var switch2 = false;
     
-    document.addEventListener('webkitTransitionEnd', function(e) {
+    document.addEventListener('transitionend', function(e) {
       var id = "1";
       if (switch1) {
         id = "2";
diff --git a/third_party/WebKit/ManualTests/transition-left.html b/third_party/WebKit/ManualTests/transition-left.html
index 492e306..7666fcd 100644
--- a/third_party/WebKit/ManualTests/transition-left.html
+++ b/third_party/WebKit/ManualTests/transition-left.html
@@ -12,8 +12,8 @@
              height: 200px;
              width: 300px;
              background-color: #9bb;
-             -webkit-transition-property: left;
-             -webkit-transition-duration: 1s;
+             transition-property: left;
+             transition-duration: 1s;
          }
      </style>
      <script type="text/javascript" charset="utf-8">
diff --git a/third_party/WebKit/ManualTests/transitions.html b/third_party/WebKit/ManualTests/transitions.html
index 33a9cff3..887ea7e 100644
--- a/third_party/WebKit/ManualTests/transitions.html
+++ b/third_party/WebKit/ManualTests/transitions.html
@@ -8,15 +8,15 @@
     div.green {
         width: 100px;
         background-color: green;
-        -webkit-transition-property: background-color;
-        -webkit-transition-duration: 5s;
+        transition-property: background-color;
+        transition-duration: 5s;
     }
 
     div.square {
         width: 100px;
         height: 100px;
-        -webkit-transition-property: height;
-        -webkit-transition-duration: 2s;
+        transition-property: height;
+        transition-duration: 2s;
     }
 </style>
 <p id="instructions">
diff --git a/third_party/WebKit/ManualTests/win/milliondollar.html b/third_party/WebKit/ManualTests/win/milliondollar.html
index e18baf9..565563e 100644
--- a/third_party/WebKit/ManualTests/win/milliondollar.html
+++ b/third_party/WebKit/ManualTests/win/milliondollar.html
@@ -12,9 +12,9 @@
     .grid {height: 1000px; width:1000px; position:relative;z-index:153;xoverflow:hidden;}
     .row {background:url(100grid_inv.png) #000;height: 100px; text-align: left; position:relative;width:1000px;z-index:10;}
     #lq {width:1000px;position:relative;z-index:0;text-align:left;}
-    .bz {-webkit-perspective: 1500;-webkit-perspective-origin: 50% 325px;}
-    .fc {-webkit-transform-style: preserve-3d;transform:  rotateY(0deg) ;opacity: 1.0;}
-    @-webkit-keyframes spin_3d {
+    .bz {perspective: 1500px;perspective-origin: 50% 325px;}
+    .fc {transform-style: preserve-3d;transform:  rotateY(0deg) ;opacity: 1.0;}
+    @keyframes spin_3d {
     0%    { transform: rotateY(0)       rotateZ(0); }
     33%   { transform: rotateY(-20deg)  rotateZ(-20deg); }
     65%   { transform: rotateY(20deg) rotateZ(20deg); }
@@ -25,7 +25,7 @@
         position: absolute;
         opacity:0.5;
         background-repeat:no-repeat;
-        -webkit-transition: -webkit-transform 0.3s, opacity 0s;-webkit-backface-visibility: visible;}
+        transition: transform 0.3s, opacity 0s;-webkit-backface-visibility: visible;}
 </style>
 
 <script type="text/javascript">
@@ -113,7 +113,7 @@
        return "<div class='a_cube'><div class='plane one' style='background-color: red;height:"+dw+"px;width:"+dw+"px;top:"+y1+"px;left:"+x1+"px;transform:rotateY(90deg) rotateZ(-90deg) translateX(0px) translateY(-"+(dw/2)+"px) translateZ("+(dw/2)+"px) translateY(0px);position:absolute;'></div><div class='plane two' style='background-color: blue;height:"+dw+"px;width:"+dw+"px;top:"+y1+"px;left:"+x1+"px;transform:rotateY(90deg) rotateX(180deg) rotateZ(-90deg) translateX(0px) translateY(-"+(dw/2)+"px) translateZ("+(dw/2)+"px) translateY(0px);position:absolute;'></div><div class='plane three' style='background-color: green;height:"+dw+"px;width:"+dw+"px;top:"+y1+"px;left:"+x1+"px;transform:rotateX(-90deg) translateX(0px) translateZ("+(dw/2)+"px) translateY(-"+(dw/2)+"px);position:absolute;'></div><div class='plane four' style='background-color: yellow;height:"+dw+"px;width:"+dw+"px;top:"+y1+"px;left:"+x1+"px;transform:rotateY(90deg) rotateX(90deg) rotateZ(-90deg) translateY(-"+(dw/2)+"px) translateX(0px) translateZ("+(dw/2)+"px) translateY(0px);position:absolute;'></div><div class='plane five' style='background-color: gray;height:"+dw+"px;width:"+dw+"px;top:"+y1+"px;left:"+x1+"px;transform:rotateY(0deg) translateX(0px) translateZ("+(dw)+"px) translateY(0px);position:absolute;'></div></div>";
     }
 
-    document.getElementById("grid").style.webkitAnimation = "spin_3d 36s infinite linear";
+    document.getElementById("grid").style.animation = "spin_3d 36s infinite linear";
 };
 
 </script>
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn
index 1a1449e..af3524a1 100644
--- a/third_party/WebKit/Source/core/BUILD.gn
+++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -244,6 +244,8 @@
     "testing/DeathAwareScriptWrappable.h",
     "testing/DictionaryTest.cpp",
     "testing/DictionaryTest.h",
+    "testing/DummyModulator.cpp",
+    "testing/DummyModulator.h",
     "testing/DummyPageHolder.cpp",
     "testing/DummyPageHolder.h",
     "testing/GCObservation.cpp",
@@ -1168,6 +1170,7 @@
     "dom/IdleDeadlineTest.cpp",
     "dom/LayoutTreeBuilderTraversalTest.cpp",
     "dom/ModulatorTest.cpp",
+    "dom/ModuleMapTest.cpp",
     "dom/MutationObserverTest.cpp",
     "dom/NodeTest.cpp",
     "dom/NthIndexCacheTest.cpp",
diff --git a/third_party/WebKit/Source/core/core_idl_files.gni b/third_party/WebKit/Source/core/core_idl_files.gni
index 8a5e782e..8130a5c 100644
--- a/third_party/WebKit/Source/core/core_idl_files.gni
+++ b/third_party/WebKit/Source/core/core_idl_files.gni
@@ -68,7 +68,7 @@
                                  "css/cssom/CSSImageValue.idl",
                                  "css/cssom/CSSKeywordValue.idl",
                                  "css/cssom/CSSLengthValue.idl",
-                                 "css/cssom/CSSMatrixTransformComponent.idl",
+                                 "css/cssom/CSSMatrixComponent.idl",
                                  "css/cssom/CSSNumberValue.idl",
                                  "css/cssom/CSSPerspective.idl",
                                  "css/cssom/CSSPositionValue.idl",
diff --git a/third_party/WebKit/Source/core/css/BUILD.gn b/third_party/WebKit/Source/core/css/BUILD.gn
index 1302b64..0066c15 100644
--- a/third_party/WebKit/Source/core/css/BUILD.gn
+++ b/third_party/WebKit/Source/core/css/BUILD.gn
@@ -254,8 +254,8 @@
     "cssom/CSSKeywordValue.h",
     "cssom/CSSLengthValue.cpp",
     "cssom/CSSLengthValue.h",
-    "cssom/CSSMatrixTransformComponent.cpp",
-    "cssom/CSSMatrixTransformComponent.h",
+    "cssom/CSSMatrixComponent.cpp",
+    "cssom/CSSMatrixComponent.h",
     "cssom/CSSNumberValue.h",
     "cssom/CSSOMKeywords.h",
     "cssom/CSSOMTypes.h",
diff --git a/third_party/WebKit/Source/core/css/CSSProperties.json5 b/third_party/WebKit/Source/core/css/CSSProperties.json5
index 15f86228..ef2d5c6b 100644
--- a/third_party/WebKit/Source/core/css/CSSProperties.json5
+++ b/third_party/WebKit/Source/core/css/CSSProperties.json5
@@ -30,7 +30,7 @@
     },
 
     // - api_class: true|"classname"
-    // Specifies the existance (and optionally name) of a CSSPropertyAPI
+    // Specifies the existence (and optionally name) of a CSSPropertyAPI
     // implementation for the property to be used by make_css_property_apis.py.
     // See core/css/properties/CSSPropertyAPI.h for API details.
     // * Add this flag if the API has been implemented for this property.
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.cpp b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.cpp
new file mode 100644
index 0000000..75c53daa
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.cpp
@@ -0,0 +1,101 @@
+// Copyright 2016 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 "core/css/cssom/CSSMatrixComponent.h"
+
+#include <cmath>
+#include <memory>
+#include "core/css/CSSPrimitiveValue.h"
+#include "wtf/MathExtras.h"
+
+namespace blink {
+
+CSSFunctionValue* CSSMatrixComponent::toCSSValue() const {
+  CSSFunctionValue* result =
+      CSSFunctionValue::create(m_is2D ? CSSValueMatrix : CSSValueMatrix3d);
+
+  if (m_is2D) {
+    double values[6] = {a(), b(), c(), d(), e(), f()};
+    for (double value : values) {
+      result->append(*CSSPrimitiveValue::create(
+          value, CSSPrimitiveValue::UnitType::Number));
+    }
+  } else {
+    double values[16] = {m11(), m12(), m13(), m14(), m21(), m22(),
+                         m23(), m24(), m31(), m32(), m33(), m34(),
+                         m41(), m42(), m43(), m44()};
+    for (double value : values) {
+      result->append(*CSSPrimitiveValue::create(
+          value, CSSPrimitiveValue::UnitType::Number));
+    }
+  }
+
+  return result;
+}
+
+CSSMatrixComponent* CSSMatrixComponent::perspective(double length) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  if (length != 0)
+    matrix->setM34(-1 / length);
+  return new CSSMatrixComponent(std::move(matrix), PerspectiveType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::rotate(double angle) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->rotate(angle);
+  return new CSSMatrixComponent(std::move(matrix), RotationType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::rotate3d(double angle,
+                                                 double x,
+                                                 double y,
+                                                 double z) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->rotate3d(x, y, z, angle);
+  return new CSSMatrixComponent(std::move(matrix), Rotation3DType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::scale(double x, double y) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->setM11(x);
+  matrix->setM22(y);
+  return new CSSMatrixComponent(std::move(matrix), ScaleType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::scale3d(double x, double y, double z) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->setM11(x);
+  matrix->setM22(y);
+  matrix->setM33(z);
+  return new CSSMatrixComponent(std::move(matrix), Scale3DType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::skew(double ax, double ay) {
+  double tanAx = std::tan(deg2rad(ax));
+  double tanAy = std::tan(deg2rad(ay));
+
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->setM12(tanAy);
+  matrix->setM21(tanAx);
+  return new CSSMatrixComponent(std::move(matrix), SkewType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::translate(double x, double y) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->setM41(x);
+  matrix->setM42(y);
+  return new CSSMatrixComponent(std::move(matrix), TranslationType);
+}
+
+CSSMatrixComponent* CSSMatrixComponent::translate3d(double x,
+                                                    double y,
+                                                    double z) {
+  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
+  matrix->setM41(x);
+  matrix->setM42(y);
+  matrix->setM43(z);
+  return new CSSMatrixComponent(std::move(matrix), Translation3DType);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.h b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.h
new file mode 100644
index 0000000..e181a4d
--- /dev/null
+++ b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.h
@@ -0,0 +1,162 @@
+// Copyright 2016 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 CSSMatrixComponent_h
+#define CSSMatrixComponent_h
+
+#include <memory>
+#include "core/css/cssom/CSSTransformComponent.h"
+#include "platform/transforms/TransformationMatrix.h"
+
+namespace blink {
+
+class CORE_EXPORT CSSMatrixComponent final : public CSSTransformComponent {
+  WTF_MAKE_NONCOPYABLE(CSSMatrixComponent);
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static CSSMatrixComponent* create(double a,
+                                    double b,
+                                    double c,
+                                    double d,
+                                    double e,
+                                    double f) {
+    return new CSSMatrixComponent(a, b, c, d, e, f);
+  }
+
+  static CSSMatrixComponent* create(double m11,
+                                    double m12,
+                                    double m13,
+                                    double m14,
+                                    double m21,
+                                    double m22,
+                                    double m23,
+                                    double m24,
+                                    double m31,
+                                    double m32,
+                                    double m33,
+                                    double m34,
+                                    double m41,
+                                    double m42,
+                                    double m43,
+                                    double m44) {
+    return new CSSMatrixComponent(m11, m12, m13, m14, m21, m22, m23, m24, m31,
+                                  m32, m33, m34, m41, m42, m43, m44);
+  }
+
+  static CSSMatrixComponent* fromCSSValue(const CSSFunctionValue& value) {
+    return nullptr;
+  }
+
+  // 2D matrix attributes
+  double a() const { return m_matrix->a(); }
+  double b() const { return m_matrix->b(); }
+  double c() const { return m_matrix->c(); }
+  double d() const { return m_matrix->d(); }
+  double e() const { return m_matrix->e(); }
+  double f() const { return m_matrix->f(); }
+
+  // 3D matrix attributes
+  double m11() const { return m_matrix->m11(); }
+  double m12() const { return m_matrix->m12(); }
+  double m13() const { return m_matrix->m13(); }
+  double m14() const { return m_matrix->m14(); }
+  double m21() const { return m_matrix->m21(); }
+  double m22() const { return m_matrix->m22(); }
+  double m23() const { return m_matrix->m23(); }
+  double m24() const { return m_matrix->m24(); }
+  double m31() const { return m_matrix->m31(); }
+  double m32() const { return m_matrix->m32(); }
+  double m33() const { return m_matrix->m33(); }
+  double m34() const { return m_matrix->m34(); }
+  double m41() const { return m_matrix->m41(); }
+  double m42() const { return m_matrix->m42(); }
+  double m43() const { return m_matrix->m43(); }
+  double m44() const { return m_matrix->m44(); }
+
+  TransformComponentType type() const override {
+    return m_is2D ? MatrixType : Matrix3DType;
+  }
+
+  // Bindings require a non const return value.
+  CSSMatrixComponent* asMatrix() const override {
+    return const_cast<CSSMatrixComponent*>(this);
+  }
+
+  CSSFunctionValue* toCSSValue() const override;
+
+  static CSSMatrixComponent* perspective(double length);
+
+  static CSSMatrixComponent* rotate(double angle);
+  static CSSMatrixComponent* rotate3d(double angle,
+                                      double x,
+                                      double y,
+                                      double z);
+
+  static CSSMatrixComponent* scale(double x, double y);
+  static CSSMatrixComponent* scale3d(double x, double y, double z);
+
+  static CSSMatrixComponent* skew(double x, double y);
+
+  static CSSMatrixComponent* translate(double x, double y);
+  static CSSMatrixComponent* translate3d(double x, double y, double z);
+
+ private:
+  CSSMatrixComponent(double a, double b, double c, double d, double e, double f)
+      : CSSTransformComponent(),
+        m_matrix(TransformationMatrix::create(a, b, c, d, e, f)),
+        m_is2D(true) {}
+
+  CSSMatrixComponent(double m11,
+                     double m12,
+                     double m13,
+                     double m14,
+                     double m21,
+                     double m22,
+                     double m23,
+                     double m24,
+                     double m31,
+                     double m32,
+                     double m33,
+                     double m34,
+                     double m41,
+                     double m42,
+                     double m43,
+                     double m44)
+      : CSSTransformComponent(),
+        m_matrix(TransformationMatrix::create(m11,
+                                              m12,
+                                              m13,
+                                              m14,
+                                              m21,
+                                              m22,
+                                              m23,
+                                              m24,
+                                              m31,
+                                              m32,
+                                              m33,
+                                              m34,
+                                              m41,
+                                              m42,
+                                              m43,
+                                              m44)),
+        m_is2D(false) {}
+
+  CSSMatrixComponent(std::unique_ptr<const TransformationMatrix> matrix,
+                     TransformComponentType fromType)
+      : CSSTransformComponent(),
+        m_matrix(std::move(matrix)),
+        m_is2D(is2DComponentType(fromType)) {}
+
+  // TransformationMatrix needs to be 16-byte aligned. PartitionAlloc
+  // supports 16-byte alignment but Oilpan doesn't. So we use an std::unique_ptr
+  // to allocate TransformationMatrix on PartitionAlloc.
+  // TODO(oilpan): Oilpan should support 16-byte aligned allocations.
+  std::unique_ptr<const TransformationMatrix> m_matrix;
+  bool m_is2D;
+};
+
+}  // namespace blink
+
+#endif
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.idl b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.idl
similarity index 94%
rename from third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.idl
rename to third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.idl
index 62ae2d5a5..b149eaf 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.idl
+++ b/third_party/WebKit/Source/core/css/cssom/CSSMatrixComponent.idl
@@ -10,7 +10,7 @@
         double m41, double m42, double m43, double m44),
     Exposed=(Window,PaintWorklet),
     RuntimeEnabled=CSSTypedOM
-] interface CSSMatrixTransformComponent : CSSTransformComponent {
+] interface CSSMatrixComponent : CSSTransformComponent {
     readonly attribute double a;
     readonly attribute double b;
     readonly attribute double c;
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.cpp b/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.cpp
deleted file mode 100644
index 33b44a5..0000000
--- a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2016 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 "core/css/cssom/CSSMatrixTransformComponent.h"
-
-#include "core/css/CSSPrimitiveValue.h"
-#include "wtf/MathExtras.h"
-#include <cmath>
-#include <memory>
-
-namespace blink {
-
-CSSFunctionValue* CSSMatrixTransformComponent::toCSSValue() const {
-  CSSFunctionValue* result =
-      CSSFunctionValue::create(m_is2D ? CSSValueMatrix : CSSValueMatrix3d);
-
-  if (m_is2D) {
-    double values[6] = {a(), b(), c(), d(), e(), f()};
-    for (double value : values) {
-      result->append(*CSSPrimitiveValue::create(
-          value, CSSPrimitiveValue::UnitType::Number));
-    }
-  } else {
-    double values[16] = {m11(), m12(), m13(), m14(), m21(), m22(),
-                         m23(), m24(), m31(), m32(), m33(), m34(),
-                         m41(), m42(), m43(), m44()};
-    for (double value : values) {
-      result->append(*CSSPrimitiveValue::create(
-          value, CSSPrimitiveValue::UnitType::Number));
-    }
-  }
-
-  return result;
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::perspective(
-    double length) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  if (length != 0)
-    matrix->setM34(-1 / length);
-  return new CSSMatrixTransformComponent(std::move(matrix), PerspectiveType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::rotate(double angle) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->rotate(angle);
-  return new CSSMatrixTransformComponent(std::move(matrix), RotationType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::rotate3d(double angle,
-                                                                   double x,
-                                                                   double y,
-                                                                   double z) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->rotate3d(x, y, z, angle);
-  return new CSSMatrixTransformComponent(std::move(matrix), Rotation3DType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::scale(double x,
-                                                                double y) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->setM11(x);
-  matrix->setM22(y);
-  return new CSSMatrixTransformComponent(std::move(matrix), ScaleType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::scale3d(double x,
-                                                                  double y,
-                                                                  double z) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->setM11(x);
-  matrix->setM22(y);
-  matrix->setM33(z);
-  return new CSSMatrixTransformComponent(std::move(matrix), Scale3DType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::skew(double ax,
-                                                               double ay) {
-  double tanAx = std::tan(deg2rad(ax));
-  double tanAy = std::tan(deg2rad(ay));
-
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->setM12(tanAy);
-  matrix->setM21(tanAx);
-  return new CSSMatrixTransformComponent(std::move(matrix), SkewType);
-}
-
-CSSMatrixTransformComponent* CSSMatrixTransformComponent::translate(double x,
-                                                                    double y) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->setM41(x);
-  matrix->setM42(y);
-  return new CSSMatrixTransformComponent(std::move(matrix), TranslationType);
-}
-
-CSSMatrixTransformComponent*
-CSSMatrixTransformComponent::translate3d(double x, double y, double z) {
-  std::unique_ptr<TransformationMatrix> matrix = TransformationMatrix::create();
-  matrix->setM41(x);
-  matrix->setM42(y);
-  matrix->setM43(z);
-  return new CSSMatrixTransformComponent(std::move(matrix), Translation3DType);
-}
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.h b/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.h
deleted file mode 100644
index 24ed6dd..0000000
--- a/third_party/WebKit/Source/core/css/cssom/CSSMatrixTransformComponent.h
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2016 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 CSSMatrixTransformComponent_h
-#define CSSMatrixTransformComponent_h
-
-#include "core/css/cssom/CSSTransformComponent.h"
-#include "platform/transforms/TransformationMatrix.h"
-#include <memory>
-
-namespace blink {
-
-class CORE_EXPORT CSSMatrixTransformComponent final
-    : public CSSTransformComponent {
-  WTF_MAKE_NONCOPYABLE(CSSMatrixTransformComponent);
-  DEFINE_WRAPPERTYPEINFO();
-
- public:
-  static CSSMatrixTransformComponent* create(double a,
-                                             double b,
-                                             double c,
-                                             double d,
-                                             double e,
-                                             double f) {
-    return new CSSMatrixTransformComponent(a, b, c, d, e, f);
-  }
-
-  static CSSMatrixTransformComponent* create(double m11,
-                                             double m12,
-                                             double m13,
-                                             double m14,
-                                             double m21,
-                                             double m22,
-                                             double m23,
-                                             double m24,
-                                             double m31,
-                                             double m32,
-                                             double m33,
-                                             double m34,
-                                             double m41,
-                                             double m42,
-                                             double m43,
-                                             double m44) {
-    return new CSSMatrixTransformComponent(m11, m12, m13, m14, m21, m22, m23,
-                                           m24, m31, m32, m33, m34, m41, m42,
-                                           m43, m44);
-  }
-
-  static CSSMatrixTransformComponent* fromCSSValue(
-      const CSSFunctionValue& value) {
-    return nullptr;
-  }
-
-  // 2D matrix attributes
-  double a() const { return m_matrix->a(); }
-  double b() const { return m_matrix->b(); }
-  double c() const { return m_matrix->c(); }
-  double d() const { return m_matrix->d(); }
-  double e() const { return m_matrix->e(); }
-  double f() const { return m_matrix->f(); }
-
-  // 3D matrix attributes
-  double m11() const { return m_matrix->m11(); }
-  double m12() const { return m_matrix->m12(); }
-  double m13() const { return m_matrix->m13(); }
-  double m14() const { return m_matrix->m14(); }
-  double m21() const { return m_matrix->m21(); }
-  double m22() const { return m_matrix->m22(); }
-  double m23() const { return m_matrix->m23(); }
-  double m24() const { return m_matrix->m24(); }
-  double m31() const { return m_matrix->m31(); }
-  double m32() const { return m_matrix->m32(); }
-  double m33() const { return m_matrix->m33(); }
-  double m34() const { return m_matrix->m34(); }
-  double m41() const { return m_matrix->m41(); }
-  double m42() const { return m_matrix->m42(); }
-  double m43() const { return m_matrix->m43(); }
-  double m44() const { return m_matrix->m44(); }
-
-  TransformComponentType type() const override {
-    return m_is2D ? MatrixType : Matrix3DType;
-  }
-
-  // Bindings require a non const return value.
-  CSSMatrixTransformComponent* asMatrix() const override {
-    return const_cast<CSSMatrixTransformComponent*>(this);
-  }
-
-  CSSFunctionValue* toCSSValue() const override;
-
-  static CSSMatrixTransformComponent* perspective(double length);
-
-  static CSSMatrixTransformComponent* rotate(double angle);
-  static CSSMatrixTransformComponent* rotate3d(double angle,
-                                               double x,
-                                               double y,
-                                               double z);
-
-  static CSSMatrixTransformComponent* scale(double x, double y);
-  static CSSMatrixTransformComponent* scale3d(double x, double y, double z);
-
-  static CSSMatrixTransformComponent* skew(double x, double y);
-
-  static CSSMatrixTransformComponent* translate(double x, double y);
-  static CSSMatrixTransformComponent* translate3d(double x, double y, double z);
-
- private:
-  CSSMatrixTransformComponent(double a,
-                              double b,
-                              double c,
-                              double d,
-                              double e,
-                              double f)
-      : CSSTransformComponent(),
-        m_matrix(TransformationMatrix::create(a, b, c, d, e, f)),
-        m_is2D(true) {}
-
-  CSSMatrixTransformComponent(double m11,
-                              double m12,
-                              double m13,
-                              double m14,
-                              double m21,
-                              double m22,
-                              double m23,
-                              double m24,
-                              double m31,
-                              double m32,
-                              double m33,
-                              double m34,
-                              double m41,
-                              double m42,
-                              double m43,
-                              double m44)
-      : CSSTransformComponent(),
-        m_matrix(TransformationMatrix::create(m11,
-                                              m12,
-                                              m13,
-                                              m14,
-                                              m21,
-                                              m22,
-                                              m23,
-                                              m24,
-                                              m31,
-                                              m32,
-                                              m33,
-                                              m34,
-                                              m41,
-                                              m42,
-                                              m43,
-                                              m44)),
-        m_is2D(false) {}
-
-  CSSMatrixTransformComponent(
-      std::unique_ptr<const TransformationMatrix> matrix,
-      TransformComponentType fromType)
-      : CSSTransformComponent(),
-        m_matrix(std::move(matrix)),
-        m_is2D(is2DComponentType(fromType)) {}
-
-  // TransformationMatrix needs to be 16-byte aligned. PartitionAlloc
-  // supports 16-byte alignment but Oilpan doesn't. So we use an std::unique_ptr
-  // to allocate TransformationMatrix on PartitionAlloc.
-  // TODO(oilpan): Oilpan should support 16-byte aligned allocations.
-  std::unique_ptr<const TransformationMatrix> m_matrix;
-  bool m_is2D;
-};
-
-}  // namespace blink
-
-#endif
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSPerspective.h b/third_party/WebKit/Source/core/css/cssom/CSSPerspective.h
index 3061c4c3..7803cf3 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSPerspective.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSPerspective.h
@@ -29,7 +29,7 @@
   TransformComponentType type() const override { return PerspectiveType; }
 
   // TODO: Implement asMatrix for CSSPerspective.
-  CSSMatrixTransformComponent* asMatrix() const override { return nullptr; }
+  CSSMatrixComponent* asMatrix() const override { return nullptr; }
 
   CSSFunctionValue* toCSSValue() const override;
 
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSRotation.h b/third_party/WebKit/Source/core/css/cssom/CSSRotation.h
index 25628f0..8c0d4d95 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSRotation.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSRotation.h
@@ -6,7 +6,7 @@
 #define CSSRotation_h
 
 #include "core/css/cssom/CSSAngleValue.h"
-#include "core/css/cssom/CSSMatrixTransformComponent.h"
+#include "core/css/cssom/CSSMatrixComponent.h"
 #include "core/css/cssom/CSSTransformComponent.h"
 
 namespace blink {
@@ -42,10 +42,10 @@
     return m_is2D ? RotationType : Rotation3DType;
   }
 
-  CSSMatrixTransformComponent* asMatrix() const override {
-    return m_is2D ? CSSMatrixTransformComponent::rotate(m_angle->degrees())
-                  : CSSMatrixTransformComponent::rotate3d(m_angle->degrees(),
-                                                          m_x, m_y, m_z);
+  CSSMatrixComponent* asMatrix() const override {
+    return m_is2D ? CSSMatrixComponent::rotate(m_angle->degrees())
+                  : CSSMatrixComponent::rotate3d(m_angle->degrees(), m_x, m_y,
+                                                 m_z);
   }
 
   CSSFunctionValue* toCSSValue() const override;
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSScale.h b/third_party/WebKit/Source/core/css/cssom/CSSScale.h
index cceb683..552f3e6 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSScale.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSScale.h
@@ -5,7 +5,7 @@
 #ifndef CSSScale_h
 #define CSSScale_h
 
-#include "core/css/cssom/CSSMatrixTransformComponent.h"
+#include "core/css/cssom/CSSMatrixComponent.h"
 #include "core/css/cssom/CSSTransformComponent.h"
 
 namespace blink {
@@ -31,9 +31,9 @@
     return m_is2D ? ScaleType : Scale3DType;
   }
 
-  CSSMatrixTransformComponent* asMatrix() const override {
-    return m_is2D ? CSSMatrixTransformComponent::scale(m_x, m_y)
-                  : CSSMatrixTransformComponent::scale3d(m_x, m_y, m_z);
+  CSSMatrixComponent* asMatrix() const override {
+    return m_is2D ? CSSMatrixComponent::scale(m_x, m_y)
+                  : CSSMatrixComponent::scale3d(m_x, m_y, m_z);
   }
 
   CSSFunctionValue* toCSSValue() const override;
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSSkew.h b/third_party/WebKit/Source/core/css/cssom/CSSSkew.h
index e7b715f..bf890e3 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSSkew.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSSkew.h
@@ -6,7 +6,7 @@
 #define CSSSkew_h
 
 #include "core/css/cssom/CSSAngleValue.h"
-#include "core/css/cssom/CSSMatrixTransformComponent.h"
+#include "core/css/cssom/CSSMatrixComponent.h"
 #include "core/css/cssom/CSSTransformComponent.h"
 
 namespace blink {
@@ -29,8 +29,8 @@
 
   TransformComponentType type() const override { return SkewType; }
 
-  CSSMatrixTransformComponent* asMatrix() const override {
-    return CSSMatrixTransformComponent::skew(m_ax->degrees(), m_ay->degrees());
+  CSSMatrixComponent* asMatrix() const override {
+    return CSSMatrixComponent::skew(m_ax->degrees(), m_ay->degrees());
   }
 
   CSSFunctionValue* toCSSValue() const override;
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.cpp b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.cpp
index 13c1531..d0d1a6f8 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.cpp
+++ b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.cpp
@@ -4,7 +4,7 @@
 
 #include "core/css/cssom/CSSTransformComponent.h"
 
-#include "core/css/cssom/CSSMatrixTransformComponent.h"
+#include "core/css/cssom/CSSMatrixComponent.h"
 #include "core/css/cssom/CSSPerspective.h"
 #include "core/css/cssom/CSSRotation.h"
 #include "core/css/cssom/CSSScale.h"
@@ -22,7 +22,7 @@
   switch (functionValue.functionType()) {
     case CSSValueMatrix:
     case CSSValueMatrix3d:
-      return CSSMatrixTransformComponent::fromCSSValue(functionValue);
+      return CSSMatrixComponent::fromCSSValue(functionValue);
     case CSSValuePerspective:
       return CSSPerspective::fromCSSValue(functionValue);
     case CSSValueRotate:
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.h b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.h
index 22ee7d64..0ba5747 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.h
@@ -12,7 +12,7 @@
 
 namespace blink {
 
-class CSSMatrixTransformComponent;
+class CSSMatrixComponent;
 
 class CORE_EXPORT CSSTransformComponent
     : public GarbageCollectedFinalized<CSSTransformComponent>,
@@ -51,7 +51,7 @@
   String cssText() const { return toCSSValue()->cssText(); }
 
   virtual CSSFunctionValue* toCSSValue() const = 0;
-  virtual CSSMatrixTransformComponent* asMatrix() const = 0;
+  virtual CSSMatrixComponent* asMatrix() const = 0;
 
   DEFINE_INLINE_VIRTUAL_TRACE() {}
 
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.idl b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.idl
index 8173d5e00..74da28ea4 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.idl
+++ b/third_party/WebKit/Source/core/css/cssom/CSSTransformComponent.idl
@@ -8,5 +8,5 @@
 ] interface CSSTransformComponent {
     readonly attribute DOMString cssText;
     boolean is2D();
-    CSSMatrixTransformComponent asMatrix();
+    CSSMatrixComponent asMatrix();
 };
diff --git a/third_party/WebKit/Source/core/css/cssom/CSSTranslation.h b/third_party/WebKit/Source/core/css/cssom/CSSTranslation.h
index 41a32f4..f08bda4 100644
--- a/third_party/WebKit/Source/core/css/cssom/CSSTranslation.h
+++ b/third_party/WebKit/Source/core/css/cssom/CSSTranslation.h
@@ -40,7 +40,7 @@
   }
 
   // TODO: Implement asMatrix for CSSTranslation.
-  CSSMatrixTransformComponent* asMatrix() const override { return nullptr; }
+  CSSMatrixComponent* asMatrix() const override { return nullptr; }
 
   CSSFunctionValue* toCSSValue() const override;
 
diff --git a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
index de20d7cc..52e4430 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSPropertyParser.cpp
@@ -337,12 +337,6 @@
   return settings;
 }
 
-static CSSValue* consumeWebkitHighlight(CSSParserTokenRange& range) {
-  if (range.peek().id() == CSSValueNone)
-    return consumeIdent(range);
-  return consumeString(range);
-}
-
 static CSSIdentifierValue* consumeFontVariantCSS21(CSSParserTokenRange& range) {
   return consumeIdent<CSSValueNormal, CSSValueSmallCaps>(range);
 }
@@ -2056,8 +2050,6 @@
     return cssPropertyDesc.parseSingleValue(m_range, m_context);
 
   switch (property) {
-    case CSSPropertyWebkitHighlight:
-      return consumeWebkitHighlight(m_range);
     case CSSPropertyFontFeatureSettings:
       return consumeFontFeatureSettings(m_range);
     case CSSPropertyFontFamily:
diff --git a/third_party/WebKit/Source/core/dom/BUILD.gn b/third_party/WebKit/Source/core/dom/BUILD.gn
index 09ff510..51a55eb 100644
--- a/third_party/WebKit/Source/core/dom/BUILD.gn
+++ b/third_party/WebKit/Source/core/dom/BUILD.gn
@@ -183,6 +183,8 @@
     "MessagePort.cpp",
     "Modulator.cpp",
     "Modulator.h",
+    "ModuleMap.cpp",
+    "ModuleMap.h",
     "ModuleScript.cpp",
     "ModuleScript.h",
     "MutationCallback.h",
@@ -246,6 +248,7 @@
     "ScopedWindowFocusAllowedIndicator.h",
     "ScriptLoader.cpp",
     "ScriptLoader.h",
+    "ScriptModuleResolver.h",
     "ScriptRunner.cpp",
     "ScriptRunner.h",
     "ScriptableDocumentParser.cpp",
diff --git a/third_party/WebKit/Source/core/dom/ElementVisibilityObserverTest.cpp b/third_party/WebKit/Source/core/dom/ElementVisibilityObserverTest.cpp
index 02f03b0..efff024 100644
--- a/third_party/WebKit/Source/core/dom/ElementVisibilityObserverTest.cpp
+++ b/third_party/WebKit/Source/core/dom/ElementVisibilityObserverTest.cpp
@@ -21,7 +21,7 @@
 // alow callers to set the parent/top frames by calling |setParent|. It is used
 // in ElementVisibilityObserverTest in order to mock a RemoteFrame parent of a
 // LocalFrame.
-class StubFrameLoaderClient final : public EmptyFrameLoaderClient {
+class StubLocalFrameClient final : public EmptyLocalFrameClient {
  public:
   Frame* parent() const override { return m_parent; }
   Frame* top() const override { return m_parent; }
@@ -30,7 +30,7 @@
 
   DEFINE_INLINE_VIRTUAL_TRACE() {
     visitor->trace(m_parent);
-    EmptyFrameLoaderClient::trace(visitor);
+    EmptyLocalFrameClient::trace(visitor);
   }
 
  private:
@@ -40,9 +40,9 @@
 class ElementVisibilityObserverTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    m_frameLoaderClient = new StubFrameLoaderClient();
+    m_localFrameClient = new StubLocalFrameClient();
     m_dummyPageHolder = DummyPageHolder::create(
-        IntSize(), nullptr, m_frameLoaderClient, nullptr, nullptr);
+        IntSize(), nullptr, m_localFrameClient, nullptr, nullptr);
   }
 
   void TearDown() override {
@@ -51,13 +51,11 @@
 
   Document& document() { return m_dummyPageHolder->document(); }
   FrameHost& frameHost() { return m_dummyPageHolder->page().frameHost(); }
-  StubFrameLoaderClient* frameLoaderClient() const {
-    return m_frameLoaderClient;
-  }
+  StubLocalFrameClient* localFrameClient() const { return m_localFrameClient; }
 
  private:
   std::unique_ptr<DummyPageHolder> m_dummyPageHolder;
-  Persistent<StubFrameLoaderClient> m_frameLoaderClient;
+  Persistent<StubLocalFrameClient> m_localFrameClient;
 };
 
 TEST_F(ElementVisibilityObserverTest, ObserveElementWithoutDocumentFrame) {
@@ -73,7 +71,7 @@
 TEST_F(ElementVisibilityObserverTest, ObserveElementInRemoteFrame) {
   Persistent<RemoteFrame> remoteFrame =
       RemoteFrame::create(new EmptyRemoteFrameClient(), &frameHost(), nullptr);
-  frameLoaderClient()->setParent(remoteFrame);
+  localFrameClient()->setParent(remoteFrame);
 
   Persistent<HTMLElement> element = HTMLDivElement::create(document());
   ElementVisibilityObserver* observer =
diff --git a/third_party/WebKit/Source/core/dom/Modulator.h b/third_party/WebKit/Source/core/dom/Modulator.h
index 6f923196..393d2557 100644
--- a/third_party/WebKit/Source/core/dom/Modulator.h
+++ b/third_party/WebKit/Source/core/dom/Modulator.h
@@ -12,6 +12,23 @@
 namespace blink {
 
 class LocalFrame;
+class ModuleScript;
+class ModuleScriptFetchRequest;
+class ModuleScriptLoaderClient;
+class ScriptModuleResolver;
+class WebTaskRunner;
+
+// A SingleModuleClient is notified when single module script node (node as in a
+// module tree graph) load is complete and its corresponding entry is created in
+// module map.
+class SingleModuleClient : public GarbageCollectedMixin {
+ public:
+  virtual void notifyModuleLoadFinished(ModuleScript*) = 0;
+};
+
+// spec: "top-level module fetch flag"
+// https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts-is-top-level
+enum class ModuleGraphLevel { TopLevelModuleFetch, DependentModuleFetch };
 
 // A Modulator is an interface for "environment settings object" concept for
 // module scripts.
@@ -22,9 +39,23 @@
  public:
   static Modulator* from(LocalFrame*);
 
+  virtual ScriptModuleResolver* scriptModuleResolver() = 0;
+  virtual WebTaskRunner* taskRunner() = 0;
+
   // https://html.spec.whatwg.org/#resolve-a-module-specifier
   static KURL resolveModuleSpecifier(const String& moduleRequest,
                                      const KURL& baseURL);
+
+ private:
+  friend class ModuleMap;
+
+  // Fetches a single module script.
+  // This is triggered from fetchSingle() implementation (which is in ModuleMap)
+  // if the cached entry doesn't exist.
+  // The client can be notified either synchronously or asynchronously.
+  virtual void fetchNewSingleModule(const ModuleScriptFetchRequest&,
+                                    ModuleGraphLevel,
+                                    ModuleScriptLoaderClient*) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/ModuleMap.cpp b/third_party/WebKit/Source/core/dom/ModuleMap.cpp
new file mode 100644
index 0000000..e68643d4
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/ModuleMap.cpp
@@ -0,0 +1,146 @@
+// 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 "core/dom/ModuleMap.h"
+
+#include "core/dom/Modulator.h"
+#include "core/dom/ModuleScript.h"
+#include "core/dom/ScriptModuleResolver.h"
+#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
+#include "core/loader/modulescript/ModuleScriptLoaderClient.h"
+#include "platform/WebTaskRunner.h"
+
+namespace blink {
+
+// Entry struct represents a value in "module map" spec object.
+// https://html.spec.whatwg.org/multipage/webappapis.html#module-map
+class ModuleMap::Entry final : public GarbageCollectedFinalized<Entry>,
+                               public ModuleScriptLoaderClient {
+  USING_GARBAGE_COLLECTED_MIXIN(ModuleMap::Entry);
+
+ public:
+  static Entry* create(ModuleMap* map) { return new Entry(map); }
+  ~Entry() override {}
+
+  DECLARE_TRACE();
+
+  // Notify fetched |m_moduleScript| to the client asynchronously.
+  void addClient(SingleModuleClient*);
+
+  // This is only to be used from ScriptModuleResolver implementations.
+  ModuleScript* getModuleScript() const;
+
+ private:
+  explicit Entry(ModuleMap*);
+
+  void dispatchFinishedNotificationAsync(SingleModuleClient*);
+
+  // Implements ModuleScriptLoaderClient
+  void notifyNewSingleModuleFinished(ModuleScript*) override;
+
+  Member<ModuleScript> m_moduleScript;
+  Member<ModuleMap> m_map;
+
+  // Correspond to the HTML spec: "fetching" state.
+  bool m_isFetching = true;
+
+  HeapHashSet<Member<SingleModuleClient>> m_clients;
+};
+
+ModuleMap::Entry::Entry(ModuleMap* map) : m_map(map) {
+  DCHECK(m_map);
+}
+
+DEFINE_TRACE(ModuleMap::Entry) {
+  visitor->trace(m_moduleScript);
+  visitor->trace(m_map);
+  visitor->trace(m_clients);
+}
+
+void ModuleMap::Entry::dispatchFinishedNotificationAsync(
+    SingleModuleClient* client) {
+  m_map->modulator()->taskRunner()->postTask(
+      BLINK_FROM_HERE,
+      WTF::bind(&SingleModuleClient::notifyModuleLoadFinished,
+                wrapPersistent(client), wrapPersistent(m_moduleScript.get())));
+}
+
+void ModuleMap::Entry::addClient(SingleModuleClient* newClient) {
+  DCHECK(!m_clients.contains(newClient));
+  if (!m_isFetching) {
+    DCHECK(m_clients.isEmpty());
+    dispatchFinishedNotificationAsync(newClient);
+    return;
+  }
+
+  m_clients.insert(newClient);
+}
+
+void ModuleMap::Entry::notifyNewSingleModuleFinished(
+    ModuleScript* moduleScript) {
+  CHECK(m_isFetching);
+  m_moduleScript = moduleScript;
+  m_isFetching = false;
+
+  if (m_moduleScript) {
+    m_map->modulator()->scriptModuleResolver()->registerModuleScript(
+        m_moduleScript);
+  }
+
+  for (const auto& client : m_clients) {
+    dispatchFinishedNotificationAsync(client);
+  }
+  m_clients.clear();
+}
+
+ModuleScript* ModuleMap::Entry::getModuleScript() const {
+  DCHECK(!m_isFetching);
+  return m_moduleScript.get();
+}
+
+ModuleMap::ModuleMap(Modulator* modulator) : m_modulator(modulator) {
+  DCHECK(modulator);
+}
+
+DEFINE_TRACE(ModuleMap) {
+  visitor->trace(m_map);
+  visitor->trace(m_modulator);
+}
+
+void ModuleMap::fetchSingleModuleScript(const ModuleScriptFetchRequest& request,
+                                        ModuleGraphLevel level,
+                                        SingleModuleClient* client) {
+  // https://html.spec.whatwg.org/#fetch-a-single-module-script
+
+  // Step 1. Let moduleMap be module map settings object's module map.
+  // Note: This is the ModuleMap.
+
+  // Step 2. If moduleMap[url] is "fetching", wait in parallel until that
+  // entry's value changes, then queue a task on the networking task source to
+  // proceed with running the following steps.
+  MapImpl::AddResult result = m_map.insert(request.url(), nullptr);
+  Member<Entry>& entry = result.storedValue->value;
+  if (result.isNewEntry) {
+    entry = Entry::create(this);
+
+    // Steps 4-9 loads a new single module script.
+    // Delegates to ModuleScriptLoader via Modulator.
+    m_modulator->fetchNewSingleModule(request, level, entry);
+  }
+  DCHECK(entry);
+
+  // Step 3. If moduleMap[url] exists, asynchronously complete this algorithm
+  // with moduleMap[url], and abort these steps.
+  // Step 10. Set moduleMap[url] to module script, and asynchronously complete
+  // this algorithm with module script.
+  entry->addClient(client);
+}
+
+ModuleScript* ModuleMap::getFetchedModuleScript(const KURL& url) const {
+  MapImpl::const_iterator it = m_map.find(url);
+  CHECK_NE(it, m_map.end());
+  return it->value->getModuleScript();
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/ModuleMap.h b/third_party/WebKit/Source/core/dom/ModuleMap.h
new file mode 100644
index 0000000..742bca5
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/ModuleMap.h
@@ -0,0 +1,60 @@
+// 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 ModuleMap_h
+#define ModuleMap_h
+
+#include "core/CoreExport.h"
+#include "platform/heap/Handle.h"
+#include "platform/weborigin/KURL.h"
+#include "platform/weborigin/KURLHash.h"
+#include "wtf/HashMap.h"
+
+namespace blink {
+
+class Modulator;
+class ModuleScript;
+class ModuleScriptFetchRequest;
+class SingleModuleClient;
+enum class ModuleGraphLevel;
+
+// A ModuleMap implements "module map" spec.
+// https://html.spec.whatwg.org/#module-map
+class CORE_EXPORT ModuleMap final : public GarbageCollected<ModuleMap> {
+  WTF_MAKE_NONCOPYABLE(ModuleMap);
+  class Entry;
+  class LoaderHost;
+
+ public:
+  static ModuleMap* create(Modulator* modulator) {
+    return new ModuleMap(modulator);
+  }
+  DECLARE_TRACE();
+
+  // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
+  void fetchSingleModuleScript(const ModuleScriptFetchRequest&,
+                               ModuleGraphLevel,
+                               SingleModuleClient*);
+
+  // Synchronously get the ModuleScript for a given URL.
+  // Note: fetchSingleModuleScript of the ModuleScript must be complete before
+  // calling this.
+  ModuleScript* getFetchedModuleScript(const KURL&) const;
+
+  Modulator* modulator() { return m_modulator; }
+
+ private:
+  explicit ModuleMap(Modulator*);
+
+  using MapImpl = HeapHashMap<KURL, Member<Entry>>;
+
+  // A module map is a map of absolute URLs to map entry.
+  MapImpl m_map;
+
+  Member<Modulator> m_modulator;
+};
+
+}  // namespace blink
+
+#endif
diff --git a/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp
new file mode 100644
index 0000000..ac18a99
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp
@@ -0,0 +1,248 @@
+// 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 "core/dom/ModuleMap.h"
+
+#include "core/dom/Document.h"
+#include "core/dom/Modulator.h"
+#include "core/dom/ModuleScript.h"
+#include "core/dom/ScriptModuleResolver.h"
+#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
+#include "core/loader/modulescript/ModuleScriptLoaderClient.h"
+#include "core/testing/DummyModulator.h"
+#include "core/testing/DummyPageHolder.h"
+#include "platform/heap/Handle.h"
+#include "platform/testing/TestingPlatformSupport.h"
+#include "public/platform/Platform.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+namespace {
+
+class TestSingleModuleClient final
+    : public GarbageCollectedFinalized<TestSingleModuleClient>,
+      public SingleModuleClient {
+  USING_GARBAGE_COLLECTED_MIXIN(TestSingleModuleClient);
+
+ public:
+  TestSingleModuleClient() = default;
+  virtual ~TestSingleModuleClient() {}
+
+  DEFINE_INLINE_TRACE() { visitor->trace(m_moduleScript); }
+
+  void notifyModuleLoadFinished(ModuleScript* moduleScript) override {
+    m_wasNotifyFinished = true;
+    m_moduleScript = moduleScript;
+  }
+
+  bool wasNotifyFinished() const { return m_wasNotifyFinished; }
+  ModuleScript* moduleScript() { return m_moduleScript; }
+
+ private:
+  bool m_wasNotifyFinished = false;
+  Member<ModuleScript> m_moduleScript;
+};
+
+class TestScriptModuleResolver final : public ScriptModuleResolver {
+ public:
+  TestScriptModuleResolver() {}
+
+  int registerModuleScriptCallCount() const {
+    return m_registerModuleScriptCallCount;
+  }
+
+  void registerModuleScript(ModuleScript*) override {
+    m_registerModuleScriptCallCount++;
+  }
+
+  ScriptModule resolve(const String& specifier,
+                       const ScriptModule& referrer,
+                       ExceptionState&) override {
+    NOTREACHED();
+    return ScriptModule();
+  }
+
+ private:
+  int m_registerModuleScriptCallCount = 0;
+};
+
+}  // namespace
+
+class ModuleMapTestModulator final : public DummyModulator {
+ public:
+  ModuleMapTestModulator();
+  virtual ~ModuleMapTestModulator() {}
+
+  DECLARE_TRACE();
+
+  TestScriptModuleResolver* testScriptModuleResolver() {
+    return m_resolver.get();
+  }
+  void resolveFetches();
+
+ private:
+  // Implements Modulator:
+
+  ScriptModuleResolver* scriptModuleResolver() override {
+    return m_resolver.get();
+  }
+
+  WebTaskRunner* taskRunner() override {
+    return Platform::current()->currentThread()->getWebTaskRunner();
+  };
+
+  void fetchNewSingleModule(const ModuleScriptFetchRequest&,
+                            ModuleGraphLevel,
+                            ModuleScriptLoaderClient*) override;
+
+  struct TestRequest : public GarbageCollectedFinalized<TestRequest> {
+    KURL url;
+    String nonce;
+    Member<ModuleScriptLoaderClient> client;
+
+    DEFINE_INLINE_TRACE() { visitor->trace(client); }
+  };
+  HeapVector<Member<TestRequest>> m_testRequests;
+
+  Member<TestScriptModuleResolver> m_resolver;
+};
+
+ModuleMapTestModulator::ModuleMapTestModulator()
+    : m_resolver(new TestScriptModuleResolver) {}
+
+DEFINE_TRACE(ModuleMapTestModulator) {
+  visitor->trace(m_testRequests);
+  visitor->trace(m_resolver);
+  DummyModulator::trace(visitor);
+}
+
+void ModuleMapTestModulator::fetchNewSingleModule(
+    const ModuleScriptFetchRequest& request,
+    ModuleGraphLevel,
+    ModuleScriptLoaderClient* client) {
+  TestRequest* testRequest = new TestRequest;
+  testRequest->url = request.url();
+  testRequest->nonce = request.nonce();
+  testRequest->client = client;
+  m_testRequests.push_back(testRequest);
+}
+
+void ModuleMapTestModulator::resolveFetches() {
+  for (const auto& testRequest : m_testRequests) {
+    ModuleScript* moduleScript = ModuleScript::create(
+        ScriptModule(), testRequest->url, testRequest->nonce, ParserInserted,
+        WebURLRequest::FetchCredentialsModeOmit);
+    taskRunner()->postTask(
+        BLINK_FROM_HERE,
+        WTF::bind(&ModuleScriptLoaderClient::notifyNewSingleModuleFinished,
+                  wrapPersistent(testRequest->client.get()),
+                  wrapPersistent(moduleScript)));
+  }
+  m_testRequests.clear();
+}
+
+class ModuleMapTest : public testing::Test {
+ public:
+  void SetUp() override;
+
+  ModuleMapTestModulator* modulator() { return m_modulator.get(); }
+  ModuleMap* map() { return m_map; }
+
+ protected:
+  Persistent<ModuleMapTestModulator> m_modulator;
+  Persistent<ModuleMap> m_map;
+};
+
+void ModuleMapTest::SetUp() {
+  m_modulator = new ModuleMapTestModulator();
+  m_map = ModuleMap::create(m_modulator.get());
+}
+
+TEST_F(ModuleMapTest, sequentialRequests) {
+  ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
+      platform;
+  platform->advanceClockSeconds(1.);  // For non-zero DocumentParserTimings
+
+  KURL url(KURL(), "https://example.com/foo.js");
+  ModuleScriptFetchRequest moduleRequest(
+      url, String(), ParserInserted, WebURLRequest::FetchCredentialsModeOmit);
+
+  // First request
+  TestSingleModuleClient* client = new TestSingleModuleClient;
+  map()->fetchSingleModuleScript(moduleRequest,
+                                 ModuleGraphLevel::TopLevelModuleFetch, client);
+  modulator()->resolveFetches();
+  EXPECT_FALSE(client->wasNotifyFinished())
+      << "fetchSingleModuleScript shouldn't complete synchronously";
+  platform->runUntilIdle();
+
+  EXPECT_EQ(
+      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(),
+      1);
+  EXPECT_TRUE(client->wasNotifyFinished());
+  EXPECT_TRUE(client->moduleScript());
+  EXPECT_EQ(client->moduleScript()->instantiationState(),
+            ModuleInstantiationState::Uninstantiated);
+
+  // Secondary request
+  TestSingleModuleClient* client2 = new TestSingleModuleClient;
+  map()->fetchSingleModuleScript(
+      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2);
+  modulator()->resolveFetches();
+  EXPECT_FALSE(client2->wasNotifyFinished())
+      << "fetchSingleModuleScript shouldn't complete synchronously";
+  platform->runUntilIdle();
+
+  EXPECT_EQ(
+      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(),
+      1)
+      << "registerModuleScript sholudn't be called in secondary request.";
+  EXPECT_TRUE(client2->wasNotifyFinished());
+  EXPECT_TRUE(client2->moduleScript());
+  EXPECT_EQ(client2->moduleScript()->instantiationState(),
+            ModuleInstantiationState::Uninstantiated);
+}
+
+TEST_F(ModuleMapTest, concurrentRequestsShouldJoin) {
+  ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
+      platform;
+  platform->advanceClockSeconds(1.);  // For non-zero DocumentParserTimings
+
+  KURL url(KURL(), "https://example.com/foo.js");
+  ModuleScriptFetchRequest moduleRequest(
+      url, String(), ParserInserted, WebURLRequest::FetchCredentialsModeOmit);
+
+  // First request
+  TestSingleModuleClient* client = new TestSingleModuleClient;
+  map()->fetchSingleModuleScript(moduleRequest,
+                                 ModuleGraphLevel::TopLevelModuleFetch, client);
+
+  // Secondary request (which should join the first request)
+  TestSingleModuleClient* client2 = new TestSingleModuleClient;
+  map()->fetchSingleModuleScript(
+      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2);
+
+  modulator()->resolveFetches();
+  EXPECT_FALSE(client->wasNotifyFinished())
+      << "fetchSingleModuleScript shouldn't complete synchronously";
+  EXPECT_FALSE(client2->wasNotifyFinished())
+      << "fetchSingleModuleScript shouldn't complete synchronously";
+  platform->runUntilIdle();
+
+  EXPECT_EQ(
+      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(),
+      1);
+
+  EXPECT_TRUE(client->wasNotifyFinished());
+  EXPECT_TRUE(client->moduleScript());
+  EXPECT_EQ(client->moduleScript()->instantiationState(),
+            ModuleInstantiationState::Uninstantiated);
+  EXPECT_TRUE(client2->wasNotifyFinished());
+  EXPECT_TRUE(client2->moduleScript());
+  EXPECT_EQ(client2->moduleScript()->instantiationState(),
+            ModuleInstantiationState::Uninstantiated);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/PendingScript.cpp b/third_party/WebKit/Source/core/dom/PendingScript.cpp
index cdcf2948..7ea0742 100644
--- a/third_party/WebKit/Source/core/dom/PendingScript.cpp
+++ b/third_party/WebKit/Source/core/dom/PendingScript.cpp
@@ -221,19 +221,13 @@
   MemoryCoordinatorClient::trace(visitor);
 }
 
-NOINLINE ScriptSourceCode PendingScript::getSource(const KURL& documentURL,
-                                                   bool& errorOccurred) const {
+ScriptSourceCode PendingScript::getSource(const KURL& documentURL,
+                                          bool& errorOccurred) const {
   checkState();
 
   errorOccurred = this->errorOccurred();
   if (resource()) {
-    // For investigating https://crbug.com/692856.
-    CHECK(resource()->isLoaded() ||
-          (resource()->isLoading() && resource()->hasRevalidated()));
-    CHECK(resource()->isLoaded() ||
-          (resource()->isLoading() && resource()->isCacheValidator()));
-    CHECK(resource()->isLoaded());
-
+    DCHECK(resource()->isLoaded());
     if (m_streamer && !m_streamer->streamingSuppressed())
       return ScriptSourceCode(m_streamer, resource());
     return ScriptSourceCode(resource());
diff --git a/third_party/WebKit/Source/core/dom/ScriptModuleResolver.h b/third_party/WebKit/Source/core/dom/ScriptModuleResolver.h
new file mode 100644
index 0000000..72816c9b
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/ScriptModuleResolver.h
@@ -0,0 +1,43 @@
+// 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 ScriptModuleResolver_h
+#define ScriptModuleResolver_h
+
+#include "bindings/core/v8/ExceptionState.h"
+#include "platform/heap/Handle.h"
+#include "wtf/text/WTFString.h"
+
+namespace blink {
+
+class ScriptModule;
+class ModuleScript;
+
+// The ScriptModuleResolver interface is used from V8 module bindings
+// when it need the ScriptModule's descendants.
+//
+// When a module writes import 'x', the module is called the referrer, 'x' is
+// the specifier, and the module identified by 'x' is the descendant.
+// ScriptModuleResolver, given a referrer and specifier, can look up the
+// descendant.
+class ScriptModuleResolver : public GarbageCollected<ScriptModuleResolver> {
+ public:
+  DEFINE_INLINE_VIRTUAL_TRACE() {}
+
+  // Notify the ScriptModuleResolver that a ModuleScript exists.
+  // This hook gives a chance for the resolver impl to populate module record
+  // identifier -> ModuleScript mapping entry.
+  virtual void registerModuleScript(ModuleScript*) = 0;
+
+  // Implements "Runtime Semantics: HostResolveImportedModule"
+  // https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
+  // This returns a null ScriptModule when an exception is thrown.
+  virtual ScriptModule resolve(const String& specifier,
+                               const ScriptModule& referrer,
+                               ExceptionState&) = 0;
+};
+
+}  // namespace blink
+
+#endif  // ScriptModuleResolver_h
diff --git a/third_party/WebKit/Source/core/editing/Editor.cpp b/third_party/WebKit/Source/core/editing/Editor.cpp
index 99b128a2..78d50f6 100644
--- a/third_party/WebKit/Source/core/editing/Editor.cpp
+++ b/third_party/WebKit/Source/core/editing/Editor.cpp
@@ -156,7 +156,6 @@
 // TODO(yosin): We should make |Editor::selectionForCommand()| to return
 // |SelectionInDOMTree| instead of |VisibleSelection|.
 VisibleSelection Editor::selectionForCommand(Event* event) {
-  frame().selection().updateIfNeeded();
   VisibleSelection selection =
       frame().selection().computeVisibleSelectionInDOMTreeDeprecated();
   if (!event)
@@ -734,9 +733,9 @@
   return hasEditableStyle(*startContainer) && hasEditableStyle(*endContainer);
 }
 
-void Editor::respondToChangedContents(const VisibleSelection& endingSelection) {
+void Editor::respondToChangedContents(const Position& position) {
   if (frame().settings() && frame().settings()->getAccessibilityEnabled()) {
-    Node* node = endingSelection.start().anchorNode();
+    Node* node = position.anchorNode();
     if (AXObjectCache* cache = frame().document()->existingAXObjectCache())
       cache->handleEditableTextContentChanged(node);
   }
@@ -917,7 +916,7 @@
     m_undoStack->registerUndoStep(m_lastEditCommand->ensureUndoStep());
   }
 
-  respondToChangedContents(newSelection);
+  respondToChangedContents(newSelection.start());
 }
 
 static VisibleSelection correctedVisibleSelection(
@@ -956,7 +955,7 @@
 
   m_lastEditCommand = nullptr;
   m_undoStack->registerRedoStep(cmd);
-  respondToChangedContents(newSelection);
+  respondToChangedContents(newSelection.start());
 }
 
 void Editor::reappliedEditing(UndoStep* cmd) {
@@ -985,7 +984,7 @@
 
   m_lastEditCommand = nullptr;
   m_undoStack->registerUndoStep(cmd);
-  respondToChangedContents(newSelection);
+  respondToChangedContents(newSelection.start());
 }
 
 Editor* Editor::create(LocalFrame& frame) {
diff --git a/third_party/WebKit/Source/core/editing/Editor.h b/third_party/WebKit/Source/core/editing/Editor.h
index dc3081d6..3d71258 100644
--- a/third_party/WebKit/Source/core/editing/Editor.h
+++ b/third_party/WebKit/Source/core/editing/Editor.h
@@ -106,7 +106,7 @@
 
   void transpose();
 
-  void respondToChangedContents(const VisibleSelection& endingSelection);
+  void respondToChangedContents(const Position&);
 
   bool selectionStartHasStyle(CSSPropertyID, const String& value) const;
   TriState selectionHasStyle(CSSPropertyID, const String& value) const;
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.cpp b/third_party/WebKit/Source/core/editing/FrameSelection.cpp
index 791876c..de34916 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelection.cpp
+++ b/third_party/WebKit/Source/core/editing/FrameSelection.cpp
@@ -1151,11 +1151,6 @@
                CursorAlignOnScroll::IfNeeded, granularity);
 }
 
-void FrameSelection::updateIfNeeded() {
-  DCHECK(!m_frame->document()->needsLayoutTreeUpdate());
-  m_selectionEditor->updateIfNeeded();
-}
-
 void FrameSelection::setCaretVisible(bool caretIsVisible) {
   m_frameCaret->setCaretVisibility(caretIsVisible ? CaretVisibility::Visible
                                                   : CaretVisibility::Hidden);
diff --git a/third_party/WebKit/Source/core/editing/FrameSelection.h b/third_party/WebKit/Source/core/editing/FrameSelection.h
index d78e98a..cb80378 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelection.h
+++ b/third_party/WebKit/Source/core/editing/FrameSelection.h
@@ -269,12 +269,6 @@
   bool shouldShowBlockCursor() const;
   void setShouldShowBlockCursor(bool);
 
-  // TODO(yosin): We should check DOM tree version and style version in
-  // |FrameSelection::selection()| to make sure we use updated selection,
-  // rather than having |updateIfNeeded()|. Once, we update all layout tests
-  // to use updated selection, we should make |updateIfNeeded()| private.
-  void updateIfNeeded();
-
   void cacheRangeOfDocument(Range*);
   Range* documentCachedRange() const;
   void clearDocumentCachedRange();
diff --git a/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp b/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
index e284266e..63aaf27a 100644
--- a/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
+++ b/third_party/WebKit/Source/core/editing/FrameSelectionTest.cpp
@@ -242,31 +242,4 @@
          "after it.";
 }
 
-TEST_F(FrameSelectionTest, updateIfNeededAndFrameCaret) {
-  setBodyContent("<style id=sample></style>");
-  document().setDesignMode("on");
-  updateAllLifecyclePhases();
-  Element* sample = document().getElementById("sample");
-  selection().setSelection(
-      SelectionInDOMTree::Builder().collapse(Position(sample, 0)).build());
-  EXPECT_EQ(Position(document().body(), 0),
-            selection().computeVisibleSelectionInDOMTreeDeprecated().start());
-  EXPECT_EQ(selection().computeVisibleSelectionInDOMTreeDeprecated().start(),
-            caretPosition().position());
-  document().body()->remove();
-  EXPECT_EQ(Position(),
-            selection().computeVisibleSelectionInDOMTreeDeprecated().start())
-      << "Selection has been removed by BODY.remove().";
-  EXPECT_EQ(selection().computeVisibleSelectionInDOMTreeDeprecated().start(),
-            caretPosition().position());
-  document().updateStyleAndLayout();
-  selection().updateIfNeeded();
-
-  EXPECT_EQ(Position(),
-            selection().computeVisibleSelectionInDOMTreeDeprecated().start())
-      << "selection().updateIfNeeded() does nothing.";
-  EXPECT_EQ(selection().computeVisibleSelectionInDOMTreeDeprecated().start(),
-            caretPosition().position());
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
index 7d38c7e..cbf5174bb 100644
--- a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
+++ b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
@@ -375,12 +375,6 @@
   return frame()->editor().behavior().shouldConsiderSelectionAsDirectional();
 }
 
-void SelectionEditor::updateIfNeeded() {
-  // TODO(yosin): We should unify |SelectionEditor::updateIfNeeded()| and
-  // |updateCachedVisibleSelectionIfNeeded()|
-  updateCachedVisibleSelectionIfNeeded();
-}
-
 bool SelectionEditor::needsUpdateVisibleSelection() const {
   return m_cacheIsDirty || m_styleVersion != document().styleVersion();
 }
diff --git a/third_party/WebKit/Source/core/editing/SelectionEditor.h b/third_party/WebKit/Source/core/editing/SelectionEditor.h
index cde5ee9..e853cfe 100644
--- a/third_party/WebKit/Source/core/editing/SelectionEditor.h
+++ b/third_party/WebKit/Source/core/editing/SelectionEditor.h
@@ -70,10 +70,6 @@
   void resetLogicalRange();
   void setLogicalRange(Range*);
 
-  // Updates |m_selection| and |m_selectionInFlatTree| with up-to-date
-  // layout if needed.
-  void updateIfNeeded();
-
   void cacheRangeOfDocument(Range*);
   Range* documentCachedRange() const;
   void clearDocumentCachedRange();
diff --git a/third_party/WebKit/Source/core/editing/commands/EditorCommand.cpp b/third_party/WebKit/Source/core/editing/commands/EditorCommand.cpp
index 21ce048f..4ebc607 100644
--- a/third_party/WebKit/Source/core/editing/commands/EditorCommand.cpp
+++ b/third_party/WebKit/Source/core/editing/commands/EditorCommand.cpp
@@ -1884,10 +1884,6 @@
                                   Event* event,
                                   EditorCommandSource) {
   frame.document()->updateStyleAndLayoutIgnorePendingStylesheets();
-
-  // We should update selection to canonicalize with current layout and style,
-  // before accessing |FrameSelection::selection()|.
-  frame.selection().updateIfNeeded();
   return frame.editor().selectionForCommand(event).rootEditableElement();
 }
 
@@ -1910,10 +1906,6 @@
                                         Event*,
                                         EditorCommandSource) {
   frame.document()->updateStyleAndLayoutIgnorePendingStylesheets();
-
-  // We should update selection to canonicalize with current layout and style,
-  // before accessing |FrameSelection::selection()|.
-  frame.selection().updateIfNeeded();
   return !frame.selection()
               .computeVisibleSelectionInDOMTreeDeprecated()
               .isNone() &&
@@ -1937,10 +1929,6 @@
                                        Event*,
                                        EditorCommandSource) {
   frame.document()->updateStyleAndLayoutIgnorePendingStylesheets();
-
-  // We should update selection to canonicalize with current layout and style,
-  // before accessing |FrameSelection::selection()|.
-  frame.selection().updateIfNeeded();
   return frame.selection()
              .computeVisibleSelectionInDOMTreeDeprecated()
              .isRange() &&
@@ -1953,10 +1941,6 @@
                                              Event*,
                                              EditorCommandSource) {
   frame.document()->updateStyleAndLayoutIgnorePendingStylesheets();
-
-  // We should update selection to canonicalize with current layout and style,
-  // before accessing |FrameSelection::selection()|.
-  frame.selection().updateIfNeeded();
   return frame.selection()
              .computeVisibleSelectionInDOMTreeDeprecated()
              .isRange() &&
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.cpp b/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.cpp
index 7230fff..cfdac39e 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.cpp
@@ -53,21 +53,13 @@
                       &IdleSpellCheckCallback::coldModeTimerFired) {}
 
 SpellCheckRequester& IdleSpellCheckCallback::spellCheckRequester() const {
-  // TODO(xiaochengh): decouple with SpellChecker after SpellCheckRequester is
-  // moved to IdleSpellCheckCallback.
   return frame().spellChecker().spellCheckRequester();
 }
 
 bool IdleSpellCheckCallback::isSpellCheckingEnabled() const {
-  // TODO(xiaochengh): decouple with SpellChecker.
   return frame().spellChecker().isSpellCheckingEnabled();
 }
 
-void IdleSpellCheckCallback::prepareForLeakDetection() {
-  if (RuntimeEnabledFeatures::idleTimeSpellCheckingEnabled())
-    spellCheckRequester().prepareForLeakDetection();
-}
-
 void IdleSpellCheckCallback::requestInvocation() {
   IdleRequestOptions options;
   options.setTimeout(kRequestTimeoutMS);
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.h b/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.h
index b3b1a93..7ddc2c4e 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.h
+++ b/third_party/WebKit/Source/core/editing/spellcheck/IdleSpellCheckCallback.h
@@ -37,14 +37,6 @@
   // Exposed for testing only.
   SpellCheckRequester& spellCheckRequester() const;
 
-  // The leak detector will report leaks should queued requests be posted
-  // while it GCs repeatedly, as the requests keep their associated element
-  // alive.
-  //
-  // Hence allow the leak detector to effectively stop the spell checker to
-  // ensure leak reporting stability.
-  void prepareForLeakDetection();
-
   DECLARE_VIRTUAL_TRACE();
 
  private:
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
index ac588cc2..bbde6b09 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
@@ -43,6 +43,7 @@
 #include "core/editing/commands/TypingCommand.h"
 #include "core/editing/iterators/CharacterIterator.h"
 #include "core/editing/markers/DocumentMarkerController.h"
+#include "core/editing/spellcheck/IdleSpellCheckCallback.h"
 #include "core/editing/spellcheck/SpellCheckRequester.h"
 #include "core/editing/spellcheck/TextCheckingParagraph.h"
 #include "core/frame/LocalFrame.h"
@@ -169,7 +170,8 @@
 
 SpellChecker::SpellChecker(LocalFrame& frame)
     : m_frame(&frame),
-      m_spellCheckRequester(SpellCheckRequester::create(frame)) {}
+      m_spellCheckRequester(SpellCheckRequester::create(frame)),
+      m_idleSpellCheckCallback(IdleSpellCheckCallback::create(frame)) {}
 
 bool SpellChecker::isSpellCheckingEnabled() const {
   return spellCheckerClient().isSpellCheckingEnabled();
@@ -1102,11 +1104,11 @@
 DEFINE_TRACE(SpellChecker) {
   visitor->trace(m_frame);
   visitor->trace(m_spellCheckRequester);
+  visitor->trace(m_idleSpellCheckCallback);
 }
 
 void SpellChecker::prepareForLeakDetection() {
-  if (!RuntimeEnabledFeatures::idleTimeSpellCheckingEnabled())
-    m_spellCheckRequester->prepareForLeakDetection();
+  m_spellCheckRequester->prepareForLeakDetection();
 }
 
 Vector<TextCheckingResult> SpellChecker::findMisspellings(const String& text) {
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.h b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.h
index 1df14ed..2fd2cf8 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.h
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.h
@@ -36,6 +36,7 @@
 namespace blink {
 
 class CompositeEditCommand;
+class IdleSpellCheckCallback;
 class LocalFrame;
 class ReplaceSelectionCommand;
 class SpellCheckerClient;
@@ -128,9 +129,8 @@
 
   Member<LocalFrame> m_frame;
 
-  // TODO(xiaochengh): Move it to IdleSpellCheckCallback after idle time spell
-  // checking reaches status=stable.
   const Member<SpellCheckRequester> m_spellCheckRequester;
+  const Member<IdleSpellCheckCallback> m_idleSpellCheckCallback;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.cpp b/third_party/WebKit/Source/core/frame/LocalFrame.cpp
index 5c03337..6739e36c 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.cpp
@@ -42,7 +42,6 @@
 #include "core/editing/FrameSelection.h"
 #include "core/editing/InputMethodController.h"
 #include "core/editing/serializers/Serialization.h"
-#include "core/editing/spellcheck/IdleSpellCheckCallback.h"
 #include "core/editing/spellcheck/SpellChecker.h"
 #include "core/events/Event.h"
 #include "core/frame/EventHandlerRegistry.h"
@@ -358,7 +357,6 @@
   visitor->trace(m_eventHandler);
   visitor->trace(m_console);
   visitor->trace(m_inputMethodController);
-  visitor->trace(m_idleSpellCheckCallback);
   Frame::trace(visitor);
   Supplementable<LocalFrame>::trace(visitor);
 }
@@ -866,7 +864,6 @@
       m_eventHandler(new EventHandler(*this)),
       m_console(FrameConsole::create(*this)),
       m_inputMethodController(InputMethodController::create(*this)),
-      m_idleSpellCheckCallback(IdleSpellCheckCallback::create(*this)),
       m_navigationDisableCount(0),
       m_pageZoomFactor(parentPageZoomFactor(this)),
       m_textZoomFactor(parentTextZoomFactor(this)),
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.h b/third_party/WebKit/Source/core/frame/LocalFrame.h
index 2336c92..fbe9ac6 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.h
@@ -58,7 +58,6 @@
 class FrameConsole;
 class FrameSelection;
 class FrameView;
-class IdleSpellCheckCallback;
 class InputMethodController;
 class InstrumentingAgents;
 class InterfaceProvider;
@@ -147,7 +146,6 @@
   ScriptController& script() const;
   SpellChecker& spellChecker() const;
   FrameConsole& console() const;
-  IdleSpellCheckCallback& idleSpellCheckCallback() const;
 
   // This method is used to get the highest level LocalFrame in this
   // frame's in-process subtree.
@@ -261,7 +259,6 @@
   const Member<EventHandler> m_eventHandler;
   const Member<FrameConsole> m_console;
   const Member<InputMethodController> m_inputMethodController;
-  const Member<IdleSpellCheckCallback> m_idleSpellCheckCallback;
 
   int m_navigationDisableCount;
 
@@ -331,11 +328,6 @@
   return *m_eventHandler;
 }
 
-inline IdleSpellCheckCallback& LocalFrame::idleSpellCheckCallback() const {
-  DCHECK(m_idleSpellCheckCallback);
-  return *m_idleSpellCheckCallback;
-}
-
 DEFINE_TYPE_CASTS(LocalFrame,
                   Frame,
                   localFrame,
diff --git a/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp b/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
index 332d2491..b1e9a3d4c 100644
--- a/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
@@ -28,13 +28,13 @@
 #include "core/events/KeyboardEvent.h"
 #include "core/events/MouseEvent.h"
 #include "core/frame/FrameHost.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/frame/UseCounter.h"
 #include "core/html/HTMLImageElement.h"
 #include "core/html/parser/HTMLParserIdioms.h"
 #include "core/layout/LayoutBox.h"
 #include "core/loader/FrameLoadRequest.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/loader/PingLoader.h"
 #include "core/page/ChromeClient.h"
 #include "platform/network/NetworkHints.h"
diff --git a/third_party/WebKit/Source/core/html/HTMLEmbedElement.cpp b/third_party/WebKit/Source/core/html/HTMLEmbedElement.cpp
index dd201c9..da88411 100644
--- a/third_party/WebKit/Source/core/html/HTMLEmbedElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLEmbedElement.cpp
@@ -29,13 +29,13 @@
 #include "core/dom/Attribute.h"
 #include "core/dom/ElementTraversal.h"
 #include "core/dom/shadow/ShadowRoot.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/html/HTMLImageLoader.h"
 #include "core/html/HTMLObjectElement.h"
 #include "core/html/PluginDocument.h"
 #include "core/html/parser/HTMLParserIdioms.h"
 #include "core/layout/LayoutPart.h"
 #include "core/layout/api/LayoutEmbeddedItem.h"
-#include "core/loader/FrameLoaderClient.h"
 
 namespace blink {
 
diff --git a/third_party/WebKit/Source/core/html/HTMLFormControlElementWithState.cpp b/third_party/WebKit/Source/core/html/HTMLFormControlElementWithState.cpp
index d26221eb..daed2a7 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormControlElementWithState.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFormControlElementWithState.cpp
@@ -26,9 +26,9 @@
 
 #include "core/frame/FrameHost.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/html/HTMLFormElement.h"
 #include "core/html/forms/FormController.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/page/ChromeClient.h"
 
 namespace blink {
diff --git a/third_party/WebKit/Source/core/html/HTMLFormElement.cpp b/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
index f8de78a..43b5606b 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
@@ -25,6 +25,7 @@
 
 #include "core/html/HTMLFormElement.h"
 
+#include <limits>
 #include "bindings/core/v8/RadioNodeListOrElement.h"
 #include "bindings/core/v8/ScriptController.h"
 #include "bindings/core/v8/ScriptEventListener.h"
@@ -37,6 +38,7 @@
 #include "core/events/ScopedEventQueue.h"
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/RemoteFrame.h"
 #include "core/frame/UseCounter.h"
 #include "core/frame/csp/ContentSecurityPolicy.h"
@@ -52,14 +54,12 @@
 #include "core/layout/LayoutObject.h"
 #include "core/loader/FormSubmission.h"
 #include "core/loader/FrameLoader.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/loader/MixedContentChecker.h"
 #include "core/loader/NavigationScheduler.h"
 #include "platform/UserGestureIndicator.h"
 #include "public/platform/WebInsecureRequestPolicy.h"
 #include "wtf/AutoReset.h"
 #include "wtf/text/AtomicString.h"
-#include <limits>
 
 namespace blink {
 
diff --git a/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp b/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp
index 88be669..05af446 100644
--- a/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp
@@ -31,12 +31,12 @@
 #include "core/dom/Document.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/RemoteFrame.h"
 #include "core/frame/RemoteFrameView.h"
 #include "core/frame/csp/ContentSecurityPolicy.h"
 #include "core/html/parser/HTMLParserIdioms.h"
 #include "core/loader/FrameLoader.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/page/FocusController.h"
 #include "core/page/Page.h"
 
diff --git a/third_party/WebKit/Source/core/html/HTMLFrameOwnerElement.cpp b/third_party/WebKit/Source/core/html/HTMLFrameOwnerElement.cpp
index ab7e818..2ce38adc 100644
--- a/third_party/WebKit/Source/core/html/HTMLFrameOwnerElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFrameOwnerElement.cpp
@@ -28,11 +28,11 @@
 #include "core/frame/FrameHost.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/layout/LayoutPart.h"
 #include "core/layout/api/LayoutPartItem.h"
 #include "core/loader/FrameLoadRequest.h"
 #include "core/loader/FrameLoader.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/plugins/PluginView.h"
 #include "platform/weborigin/SecurityOrigin.h"
 
diff --git a/third_party/WebKit/Source/core/html/HTMLFrameSetElement.cpp b/third_party/WebKit/Source/core/html/HTMLFrameSetElement.cpp
index db7c2f08..581d619 100644
--- a/third_party/WebKit/Source/core/html/HTMLFrameSetElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFrameSetElement.cpp
@@ -31,10 +31,10 @@
 #include "core/events/Event.h"
 #include "core/events/MouseEvent.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/html/HTMLCollection.h"
 #include "core/html/HTMLFrameElement.h"
 #include "core/layout/LayoutFrameSet.h"
-#include "core/loader/FrameLoaderClient.h"
 
 namespace blink {
 
diff --git a/third_party/WebKit/Source/core/html/HTMLLinkElement.cpp b/third_party/WebKit/Source/core/html/HTMLLinkElement.cpp
index 7e97fcc..989d4898 100644
--- a/third_party/WebKit/Source/core/html/HTMLLinkElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLLinkElement.cpp
@@ -31,12 +31,12 @@
 #include "core/dom/Document.h"
 #include "core/dom/TaskRunnerHelper.h"
 #include "core/events/Event.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/UseCounter.h"
 #include "core/html/CrossOriginAttribute.h"
 #include "core/html/LinkManifest.h"
 #include "core/html/imports/LinkImport.h"
 #include "core/inspector/ConsoleMessage.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/loader/NetworkHintsInterface.h"
 #include "core/origin_trials/OriginTrials.h"
 #include "platform/weborigin/SecurityPolicy.h"
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
index ee9fcef..7c10b4a 100644
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
@@ -26,6 +26,7 @@
 
 #include "core/html/HTMLMediaElement.h"
 
+#include <limits>
 #include "bindings/core/v8/ExceptionState.h"
 #include "bindings/core/v8/Microtask.h"
 #include "bindings/core/v8/ScriptController.h"
@@ -43,6 +44,7 @@
 #include "core/events/Event.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/frame/UseCounter.h"
 #include "core/frame/csp/ContentSecurityPolicy.h"
@@ -69,7 +71,6 @@
 #include "core/layout/api/LayoutViewItem.h"
 #include "core/layout/compositing/PaintLayerCompositor.h"
 #include "core/loader/FrameLoader.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/page/ChromeClient.h"
 #include "core/page/NetworkStateNotifier.h"
 #include "platform/Histogram.h"
@@ -97,7 +98,6 @@
 #include "wtf/MathExtras.h"
 #include "wtf/PtrUtil.h"
 #include "wtf/text/CString.h"
-#include <limits>
 
 #ifndef BLINK_MEDIA_LOG
 #define BLINK_MEDIA_LOG DVLOG(3)
@@ -3915,8 +3915,8 @@
   LocalFrame* frame = document().frame();
   if (!frame)
     return false;
-  FrameLoaderClient* frameLoaderClient = frame->loader().client();
-  return frameLoaderClient && frameLoaderClient->allowAutoplay(true);
+  LocalFrameClient* localFrameClient = frame->loader().client();
+  return localFrameClient && localFrameClient->allowAutoplay(true);
 }
 
 void HTMLMediaElement::setNetworkState(NetworkState state) {
diff --git a/third_party/WebKit/Source/core/html/HTMLMetaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMetaElement.cpp
index 7c1376b..b09f5764 100644
--- a/third_party/WebKit/Source/core/html/HTMLMetaElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLMetaElement.cpp
@@ -26,11 +26,11 @@
 #include "core/dom/Document.h"
 #include "core/dom/ElementTraversal.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/html/HTMLHeadElement.h"
 #include "core/html/parser/HTMLParserIdioms.h"
 #include "core/inspector/ConsoleMessage.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/loader/HttpEquiv.h"
 #include "platform/RuntimeEnabledFeatures.h"
 #include "wtf/text/StringToNumber.h"
diff --git a/third_party/WebKit/Source/core/html/HTMLObjectElement.cpp b/third_party/WebKit/Source/core/html/HTMLObjectElement.cpp
index 780bb4b..afa08e86 100644
--- a/third_party/WebKit/Source/core/html/HTMLObjectElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLObjectElement.cpp
@@ -33,13 +33,13 @@
 #include "core/dom/Text.h"
 #include "core/dom/shadow/ShadowRoot.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/html/HTMLImageLoader.h"
 #include "core/html/HTMLMetaElement.h"
 #include "core/html/HTMLParamElement.h"
 #include "core/html/parser/HTMLParserIdioms.h"
 #include "core/layout/api/LayoutEmbeddedItem.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/plugins/PluginView.h"
 #include "platform/Widget.h"
 #include "platform/network/mime/MIMETypeRegistry.h"
diff --git a/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp b/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp
index 41a2aaa9..123c9140 100644
--- a/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp
@@ -31,6 +31,7 @@
 #include "core/events/Event.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/frame/csp/ContentSecurityPolicy.h"
 #include "core/html/HTMLContentElement.h"
@@ -41,7 +42,6 @@
 #include "core/layout/LayoutImage.h"
 #include "core/layout/LayoutPart.h"
 #include "core/layout/api/LayoutEmbeddedItem.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/loader/MixedContentChecker.h"
 #include "core/page/Page.h"
 #include "core/page/scrolling/ScrollingCoordinator.h"
@@ -525,9 +525,9 @@
   } else {
     bool loadManually =
         document().isPluginDocument() && !document().containsPlugins();
-    FrameLoaderClient::DetachedPluginPolicy policy =
-        requireLayoutObject ? FrameLoaderClient::FailOnDetachedPlugin
-                            : FrameLoaderClient::AllowDetachedPlugin;
+    LocalFrameClient::DetachedPluginPolicy policy =
+        requireLayoutObject ? LocalFrameClient::FailOnDetachedPlugin
+                            : LocalFrameClient::AllowDetachedPlugin;
     Widget* widget = frame->loader().client()->createPlugin(
         this, url, paramNames, paramValues, mimeType, loadManually, policy);
     if (!widget) {
diff --git a/third_party/WebKit/Source/core/html/HTMLVideoElementTest.cpp b/third_party/WebKit/Source/core/html/HTMLVideoElementTest.cpp
index d954c0f..0d438b60 100644
--- a/third_party/WebKit/Source/core/html/HTMLVideoElementTest.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLVideoElementTest.cpp
@@ -64,9 +64,9 @@
   MOCK_METHOD1(setBufferingStrategy, void(BufferingStrategy));
 };
 
-class StubFrameLoaderClient : public EmptyFrameLoaderClient {
+class StubLocalFrameClient : public EmptyLocalFrameClient {
  public:
-  static StubFrameLoaderClient* create() { return new StubFrameLoaderClient; }
+  static StubLocalFrameClient* create() { return new StubLocalFrameClient; }
 
   std::unique_ptr<WebMediaPlayer> createWebMediaPlayer(
       HTMLMediaElement&,
@@ -84,7 +84,7 @@
       : m_dummyPageHolder(
             DummyPageHolder::create(IntSize(640, 360),
                                     nullptr,
-                                    StubFrameLoaderClient::create())) {
+                                    StubLocalFrameClient::create())) {
     // TODO(sandersd): This should be done by a settings initializer.
     networkStateNotifier().setWebConnection(WebConnectionTypeWifi, 54.0);
     m_video = HTMLVideoElement::create(m_dummyPageHolder->document());
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
index cb58274..571451c 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
@@ -79,9 +79,9 @@
   MOCK_CONST_METHOD0(screenInfo, WebScreenInfo());
 };
 
-class StubFrameLoaderClient : public EmptyFrameLoaderClient {
+class StubLocalFrameClient : public EmptyLocalFrameClient {
  public:
-  static StubFrameLoaderClient* create() { return new StubFrameLoaderClient; }
+  static StubLocalFrameClient* create() { return new StubLocalFrameClient; }
 
   std::unique_ptr<WebMediaPlayer> createWebMediaPlayer(
       HTMLMediaElement&,
@@ -144,7 +144,7 @@
     clients.chromeClient = m_chromeClient.get();
 
     m_pageHolder = DummyPageHolder::create(IntSize(800, 600), &clients,
-                                           StubFrameLoaderClient::create());
+                                           StubLocalFrameClient::create());
 
     document().write("<body><video></body>");
     m_video = toHTMLVideoElement(*document().querySelector("video"));
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlsTest.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlsTest.cpp
index d6fc73e..271c810 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlsTest.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlsTest.cpp
@@ -82,9 +82,9 @@
       WebRemotePlaybackAvailability::Unknown;
 };
 
-class StubFrameLoaderClient : public EmptyFrameLoaderClient {
+class StubLocalFrameClient : public EmptyLocalFrameClient {
  public:
-  static StubFrameLoaderClient* create() { return new StubFrameLoaderClient; }
+  static StubLocalFrameClient* create() { return new StubLocalFrameClient; }
 
   std::unique_ptr<WebMediaPlayer> createWebMediaPlayer(
       HTMLMediaElement&,
@@ -143,7 +143,7 @@
  protected:
   virtual void SetUp() {
     m_pageHolder = DummyPageHolder::create(IntSize(800, 600), nullptr,
-                                           StubFrameLoaderClient::create());
+                                           StubLocalFrameClient::create());
     Document& document = this->document();
 
     document.write("<video>");
diff --git a/third_party/WebKit/Source/core/layout/LayoutObjectTest.cpp b/third_party/WebKit/Source/core/layout/LayoutObjectTest.cpp
index 10cd9e9..0467cbb 100644
--- a/third_party/WebKit/Source/core/layout/LayoutObjectTest.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutObjectTest.cpp
@@ -15,7 +15,7 @@
 
 class LayoutObjectTest : public RenderingTest {
  public:
-  LayoutObjectTest() : RenderingTest(EmptyFrameLoaderClient::create()) {}
+  LayoutObjectTest() : RenderingTest(EmptyLocalFrameClient::create()) {}
 };
 
 TEST_F(LayoutObjectTest, LayoutDecoratedNameCalledWithPositionedObject) {
diff --git a/third_party/WebKit/Source/core/layout/LayoutTestHelper.cpp b/third_party/WebKit/Source/core/layout/LayoutTestHelper.cpp
index 3ac6e59a..edd0450 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTestHelper.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutTestHelper.cpp
@@ -16,14 +16,14 @@
 
 namespace blink {
 
-LocalFrame* SingleChildFrameLoaderClient::createFrame(
+LocalFrame* SingleChildLocalFrameClient::createFrame(
     const FrameLoadRequest&,
     const AtomicString& name,
     HTMLFrameOwnerElement* ownerElement) {
   DCHECK(!m_child) << "This test helper only supports one child frame.";
 
   LocalFrame* parentFrame = ownerElement->document().frame();
-  auto* childClient = FrameLoaderClientWithParent::create(parentFrame);
+  auto* childClient = LocalFrameClientWithParent::create(parentFrame);
   m_child = LocalFrame::create(childClient, parentFrame->host(), ownerElement);
   m_child->createView(IntSize(500, 500), Color(), true /* transparent */);
   m_child->init();
@@ -31,8 +31,8 @@
   return m_child.get();
 }
 
-void FrameLoaderClientWithParent::detached(FrameDetachType) {
-  static_cast<SingleChildFrameLoaderClient*>(parent()->client())
+void LocalFrameClientWithParent::detached(FrameDetachType) {
+  static_cast<SingleChildLocalFrameClient*>(parent()->client())
       ->didDetachChild();
 }
 
diff --git a/third_party/WebKit/Source/core/layout/LayoutTestHelper.h b/third_party/WebKit/Source/core/layout/LayoutTestHelper.h
index ffe6a7b8..24064c58 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTestHelper.h
+++ b/third_party/WebKit/Source/core/layout/LayoutTestHelper.h
@@ -5,9 +5,13 @@
 #ifndef LayoutTestHelper_h
 #define LayoutTestHelper_h
 
+#include <gtest/gtest.h>
+#include <memory>
+
 #include "core/dom/Document.h"
 #include "core/frame/FrameHost.h"
 #include "core/frame/FrameView.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/html/HTMLElement.h"
 #include "core/layout/api/LayoutAPIShim.h"
@@ -15,20 +19,18 @@
 #include "core/loader/EmptyClients.h"
 #include "core/testing/DummyPageHolder.h"
 #include "wtf/Allocator.h"
-#include <gtest/gtest.h>
-#include <memory>
 
 namespace blink {
 
-class SingleChildFrameLoaderClient final : public EmptyFrameLoaderClient {
+class SingleChildLocalFrameClient final : public EmptyLocalFrameClient {
  public:
-  static SingleChildFrameLoaderClient* create() {
-    return new SingleChildFrameLoaderClient();
+  static SingleChildLocalFrameClient* create() {
+    return new SingleChildLocalFrameClient();
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE() {
     visitor->trace(m_child);
-    EmptyFrameLoaderClient::trace(visitor);
+    EmptyLocalFrameClient::trace(visitor);
   }
 
   // FrameLoaderClient overrides:
@@ -40,20 +42,20 @@
   void didDetachChild() { m_child = nullptr; }
 
  private:
-  explicit SingleChildFrameLoaderClient() {}
+  explicit SingleChildLocalFrameClient() {}
 
   Member<LocalFrame> m_child;
 };
 
-class FrameLoaderClientWithParent final : public EmptyFrameLoaderClient {
+class LocalFrameClientWithParent final : public EmptyLocalFrameClient {
  public:
-  static FrameLoaderClientWithParent* create(LocalFrame* parent) {
-    return new FrameLoaderClientWithParent(parent);
+  static LocalFrameClientWithParent* create(LocalFrame* parent) {
+    return new LocalFrameClientWithParent(parent);
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE() {
     visitor->trace(m_parent);
-    EmptyFrameLoaderClient::trace(visitor);
+    EmptyLocalFrameClient::trace(visitor);
   }
 
   // FrameClient overrides:
@@ -61,7 +63,7 @@
   LocalFrame* parent() const override { return m_parent.get(); }
 
  private:
-  explicit FrameLoaderClientWithParent(LocalFrame* parent) : m_parent(parent) {}
+  explicit LocalFrameClientWithParent(LocalFrame* parent) : m_parent(parent) {}
 
   Member<LocalFrame> m_parent;
 };
@@ -75,7 +77,7 @@
   }
   virtual ChromeClient& chromeClient() const;
 
-  RenderingTest(FrameLoaderClient* = nullptr);
+  RenderingTest(LocalFrameClient* = nullptr);
 
  protected:
   void SetUp() override;
diff --git a/third_party/WebKit/Source/core/layout/MapCoordinatesTest.cpp b/third_party/WebKit/Source/core/layout/MapCoordinatesTest.cpp
index 3e92f19..623e8ea 100644
--- a/third_party/WebKit/Source/core/layout/MapCoordinatesTest.cpp
+++ b/third_party/WebKit/Source/core/layout/MapCoordinatesTest.cpp
@@ -19,7 +19,7 @@
  public:
   MapCoordinatesTest()
       : ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
   FloatPoint mapLocalToAncestor(const LayoutObject*,
                                 const LayoutBoxModelObject* ancestor,
                                 FloatPoint,
diff --git a/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp b/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
index 1f0486a..af9aa37 100644
--- a/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
+++ b/third_party/WebKit/Source/core/layout/VisualRectMappingTest.cpp
@@ -13,7 +13,7 @@
 class VisualRectMappingTest : public RenderingTest {
  public:
   VisualRectMappingTest()
-      : RenderingTest(SingleChildFrameLoaderClient::create()) {}
+      : RenderingTest(SingleChildLocalFrameClient::create()) {}
 
  protected:
   LayoutView& layoutView() const { return *document().layoutView(); }
diff --git a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
index 7e308c20..de3edba 100644
--- a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
+++ b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
@@ -24,7 +24,7 @@
  public:
   CompositedLayerMappingTest()
       : ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
 
  protected:
   IntRect recomputeInterestRect(const GraphicsLayer* graphicsLayer) {
diff --git a/third_party/WebKit/Source/core/layout/compositing/CompositingReasonFinderTest.cpp b/third_party/WebKit/Source/core/layout/compositing/CompositingReasonFinderTest.cpp
index 932784d7..ce5ddb7 100644
--- a/third_party/WebKit/Source/core/layout/compositing/CompositingReasonFinderTest.cpp
+++ b/third_party/WebKit/Source/core/layout/compositing/CompositingReasonFinderTest.cpp
@@ -17,7 +17,7 @@
 class CompositingReasonFinderTest : public RenderingTest {
  public:
   CompositingReasonFinderTest()
-      : RenderingTest(EmptyFrameLoaderClient::create()) {}
+      : RenderingTest(EmptyLocalFrameClient::create()) {}
 
  private:
   void SetUp() override {
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_units.cc b/third_party/WebKit/Source/core/layout/ng/ng_units.cc
index c3c21c28..b77f9b3 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_units.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_units.cc
@@ -59,6 +59,16 @@
                         size.block_size.toString().ascii().data());
 }
 
+NGPixelSnappedPhysicalRect NGPhysicalRect::SnapToDevicePixels() const {
+  NGPixelSnappedPhysicalRect snappedRect;
+  snappedRect.left = roundToInt(location.left);
+  snappedRect.top = roundToInt(location.top);
+  snappedRect.width = snapSizeToPixel(size.width, location.left);
+  snappedRect.height = snapSizeToPixel(size.height, location.top);
+
+  return snappedRect;
+}
+
 NGPhysicalOffset NGLogicalOffset::ConvertToPhysical(
     NGWritingMode mode,
     TextDirection direction,
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_units.h b/third_party/WebKit/Source/core/layout/ng/ng_units.h
index c3ed32c3..53b898a 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_units.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_units.h
@@ -17,6 +17,7 @@
 struct NGPhysicalOffset;
 struct NGPhysicalSize;
 struct NGBoxStrut;
+struct NGPixelSnappedPhysicalRect;
 
 #define NGSizeIndefinite LayoutUnit(-1)
 
@@ -150,12 +151,22 @@
 // NGPhysicalLocation is the position of a rect (typically a fragment) relative
 // to the root document.
 struct NGPhysicalLocation {
+  NGPhysicalLocation() {}
+  NGPhysicalLocation(LayoutUnit left, LayoutUnit top) : left(left), top(top) {}
   LayoutUnit left;
   LayoutUnit top;
 };
 
-struct NGPhysicalRect {
-  NGPhysicalOffset offset;
+struct CORE_EXPORT NGPhysicalRect {
+  NGPhysicalRect();
+  NGPhysicalRect(const NGPhysicalLocation& location, const NGPhysicalSize& size)
+      : location(location), size(size) {}
+
+  NGPhysicalLocation Location() const { return location; }
+  NGPhysicalSize Size() const { return size; }
+  NGPixelSnappedPhysicalRect SnapToDevicePixels() const;
+
+  NGPhysicalLocation location;
   NGPhysicalSize size;
 };
 
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_units_test.cc b/third_party/WebKit/Source/core/layout/ng/ng_units_test.cc
index 17c3fa70..bf6684ac 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_units_test.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_units_test.cc
@@ -116,6 +116,40 @@
   EXPECT_EQ(LayoutUnit(200), sizes.ShrinkToFit(LayoutUnit(100)));
 }
 
+TEST(NGUnitsTest, SnapToDevicePixels) {
+  NGPhysicalRect rect(NGPhysicalLocation(LayoutUnit(4.5), LayoutUnit(9.5)),
+                      NGPhysicalSize(LayoutUnit(10.5), LayoutUnit(11.5)));
+  NGPixelSnappedPhysicalRect snappedRect = rect.SnapToDevicePixels();
+  EXPECT_EQ(5, snappedRect.left);
+  EXPECT_EQ(10, snappedRect.top);
+  EXPECT_EQ(10, snappedRect.width);
+  EXPECT_EQ(11, snappedRect.height);
+
+  rect = NGPhysicalRect(NGPhysicalLocation(LayoutUnit(4), LayoutUnit(9)),
+                        NGPhysicalSize(LayoutUnit(10.5), LayoutUnit(11.5)));
+  snappedRect = rect.SnapToDevicePixels();
+  EXPECT_EQ(4, snappedRect.left);
+  EXPECT_EQ(9, snappedRect.top);
+  EXPECT_EQ(11, snappedRect.width);
+  EXPECT_EQ(12, snappedRect.height);
+
+  rect = NGPhysicalRect(NGPhysicalLocation(LayoutUnit(1.3), LayoutUnit(1.6)),
+                        NGPhysicalSize(LayoutUnit(5.8), LayoutUnit(4.3)));
+  snappedRect = rect.SnapToDevicePixels();
+  EXPECT_EQ(1, snappedRect.left);
+  EXPECT_EQ(2, snappedRect.top);
+  EXPECT_EQ(6, snappedRect.width);
+  EXPECT_EQ(4, snappedRect.height);
+
+  rect = NGPhysicalRect(NGPhysicalLocation(LayoutUnit(1.6), LayoutUnit(1.3)),
+                        NGPhysicalSize(LayoutUnit(5.8), LayoutUnit(4.3)));
+  snappedRect = rect.SnapToDevicePixels();
+  EXPECT_EQ(2, snappedRect.left);
+  EXPECT_EQ(1, snappedRect.top);
+  EXPECT_EQ(5, snappedRect.width);
+  EXPECT_EQ(5, snappedRect.height);
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/loader/BUILD.gn b/third_party/WebKit/Source/core/loader/BUILD.gn
index e58fc416..c7d1aa8 100644
--- a/third_party/WebKit/Source/core/loader/BUILD.gn
+++ b/third_party/WebKit/Source/core/loader/BUILD.gn
@@ -56,6 +56,7 @@
     "appcache/ApplicationCacheHost.cpp",
     "appcache/ApplicationCacheHost.h",
     "modulescript/ModuleScriptFetchRequest.h",
+    "modulescript/ModuleScriptLoaderClient.h",
     "private/CrossOriginPreflightResultCache.cpp",
     "private/CrossOriginPreflightResultCache.h",
     "private/FrameClientHintsPreferencesContext.cpp",
diff --git a/third_party/WebKit/Source/core/loader/EmptyClients.cpp b/third_party/WebKit/Source/core/loader/EmptyClients.cpp
index b3bd635..308465ad 100644
--- a/third_party/WebKit/Source/core/loader/EmptyClients.cpp
+++ b/third_party/WebKit/Source/core/loader/EmptyClients.cpp
@@ -127,7 +127,7 @@
   return WTF::makeUnique<EmptyFrameScheduler>();
 }
 
-NavigationPolicy EmptyFrameLoaderClient::decidePolicyForNavigation(
+NavigationPolicy EmptyLocalFrameClient::decidePolicyForNavigation(
     const ResourceRequest&,
     DocumentLoader*,
     NavigationType,
@@ -138,11 +138,11 @@
   return NavigationPolicyIgnore;
 }
 
-void EmptyFrameLoaderClient::dispatchWillSendSubmitEvent(HTMLFormElement*) {}
+void EmptyLocalFrameClient::dispatchWillSendSubmitEvent(HTMLFormElement*) {}
 
-void EmptyFrameLoaderClient::dispatchWillSubmitForm(HTMLFormElement*) {}
+void EmptyLocalFrameClient::dispatchWillSubmitForm(HTMLFormElement*) {}
 
-DocumentLoader* EmptyFrameLoaderClient::createDocumentLoader(
+DocumentLoader* EmptyLocalFrameClient::createDocumentLoader(
     LocalFrame* frame,
     const ResourceRequest& request,
     const SubstituteData& substituteData,
@@ -153,30 +153,30 @@
                                 clientRedirectPolicy);
 }
 
-LocalFrame* EmptyFrameLoaderClient::createFrame(const FrameLoadRequest&,
-                                                const AtomicString&,
-                                                HTMLFrameOwnerElement*) {
+LocalFrame* EmptyLocalFrameClient::createFrame(const FrameLoadRequest&,
+                                               const AtomicString&,
+                                               HTMLFrameOwnerElement*) {
   return nullptr;
 }
 
-Widget* EmptyFrameLoaderClient::createPlugin(HTMLPlugInElement*,
-                                             const KURL&,
-                                             const Vector<String>&,
-                                             const Vector<String>&,
-                                             const String&,
-                                             bool,
-                                             DetachedPluginPolicy) {
+Widget* EmptyLocalFrameClient::createPlugin(HTMLPlugInElement*,
+                                            const KURL&,
+                                            const Vector<String>&,
+                                            const Vector<String>&,
+                                            const String&,
+                                            bool,
+                                            DetachedPluginPolicy) {
   return nullptr;
 }
 
-std::unique_ptr<WebMediaPlayer> EmptyFrameLoaderClient::createWebMediaPlayer(
+std::unique_ptr<WebMediaPlayer> EmptyLocalFrameClient::createWebMediaPlayer(
     HTMLMediaElement&,
     const WebMediaPlayerSource&,
     WebMediaPlayerClient*) {
   return nullptr;
 }
 
-WebRemotePlaybackClient* EmptyFrameLoaderClient::createWebRemotePlaybackClient(
+WebRemotePlaybackClient* EmptyLocalFrameClient::createWebRemotePlaybackClient(
     HTMLMediaElement&) {
   return nullptr;
 }
@@ -186,12 +186,12 @@
 void EmptyTextCheckerClient::cancelAllPendingRequests() {}
 
 std::unique_ptr<WebServiceWorkerProvider>
-EmptyFrameLoaderClient::createServiceWorkerProvider() {
+EmptyLocalFrameClient::createServiceWorkerProvider() {
   return nullptr;
 }
 
 std::unique_ptr<WebApplicationCacheHost>
-EmptyFrameLoaderClient::createApplicationCacheHost(
+EmptyLocalFrameClient::createApplicationCacheHost(
     WebApplicationCacheHostClient*) {
   return nullptr;
 }
diff --git a/third_party/WebKit/Source/core/loader/EmptyClients.h b/third_party/WebKit/Source/core/loader/EmptyClients.h
index 24f7fcb..71531ea 100644
--- a/third_party/WebKit/Source/core/loader/EmptyClients.h
+++ b/third_party/WebKit/Source/core/loader/EmptyClients.h
@@ -29,9 +29,11 @@
 #ifndef EmptyClients_h
 #define EmptyClients_h
 
+#include <memory>
+
 #include "core/CoreExport.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/RemoteFrameClient.h"
-#include "core/loader/FrameLoaderClient.h"
 #include "core/page/ChromeClient.h"
 #include "core/page/ContextMenuClient.h"
 #include "core/page/EditorClient.h"
@@ -47,9 +49,8 @@
 #include "platform/text/TextCheckerClient.h"
 #include "public/platform/WebFocusType.h"
 #include "public/platform/WebScreenInfo.h"
+#include "v8/include/v8.h"
 #include "wtf/Forward.h"
-#include <memory>
-#include <v8.h>
 
 /*
  This file holds empty Client stubs for use by WebCore.
@@ -235,12 +236,12 @@
       BlameContext*) override;
 };
 
-class CORE_EXPORT EmptyFrameLoaderClient : public FrameLoaderClient {
-  WTF_MAKE_NONCOPYABLE(EmptyFrameLoaderClient);
+class CORE_EXPORT EmptyLocalFrameClient : public LocalFrameClient {
+  WTF_MAKE_NONCOPYABLE(EmptyLocalFrameClient);
 
  public:
-  static EmptyFrameLoaderClient* create() { return new EmptyFrameLoaderClient; }
-  ~EmptyFrameLoaderClient() override {}
+  static EmptyLocalFrameClient* create() { return new EmptyLocalFrameClient; }
+  ~EmptyLocalFrameClient() override {}
 
   bool hasWebView() const override { return true; }  // mainly for assertions
 
@@ -365,7 +366,7 @@
       WebApplicationCacheHostClient*) override;
 
  protected:
-  EmptyFrameLoaderClient() {}
+  EmptyLocalFrameClient() {}
 };
 
 class CORE_EXPORT EmptyTextCheckerClient : public TextCheckerClient {
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
index 3f78de4..31c5c3d4 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContextTest.cpp
@@ -56,28 +56,28 @@
 
 namespace blink {
 
-class StubFrameLoaderClientWithParent final : public EmptyFrameLoaderClient {
+class StubLocalFrameClientWithParent final : public EmptyLocalFrameClient {
  public:
-  static StubFrameLoaderClientWithParent* create(Frame* parent) {
-    return new StubFrameLoaderClientWithParent(parent);
+  static StubLocalFrameClientWithParent* create(Frame* parent) {
+    return new StubLocalFrameClientWithParent(parent);
   }
 
   DEFINE_INLINE_VIRTUAL_TRACE() {
     visitor->trace(m_parent);
-    EmptyFrameLoaderClient::trace(visitor);
+    EmptyLocalFrameClient::trace(visitor);
   }
 
   Frame* parent() const override { return m_parent.get(); }
 
  private:
-  explicit StubFrameLoaderClientWithParent(Frame* parent) : m_parent(parent) {}
+  explicit StubLocalFrameClientWithParent(Frame* parent) : m_parent(parent) {}
 
   Member<Frame> m_parent;
 };
 
-class MockFrameLoaderClient : public EmptyFrameLoaderClient {
+class MockLocalFrameClient : public EmptyLocalFrameClient {
  public:
-  MockFrameLoaderClient() : EmptyFrameLoaderClient() {}
+  MockLocalFrameClient() : EmptyLocalFrameClient() {}
   MOCK_METHOD1(didDisplayContentWithCertificateErrors, void(const KURL&));
   MOCK_METHOD2(dispatchDidLoadResourceFromMemoryCache,
                void(const ResourceRequest&, const ResourceResponse&));
@@ -118,7 +118,7 @@
   }
 
   FrameFetchContext* createChildFrame() {
-    childClient = StubFrameLoaderClientWithParent::create(document->frame());
+    childClient = StubLocalFrameClientWithParent::create(document->frame());
     childFrame = LocalFrame::create(childClient.get(),
                                     document->frame()->host(), owner.get());
     childFrame->setView(FrameView::create(*childFrame, IntSize(500, 500)));
@@ -138,7 +138,7 @@
   Persistent<Document> document;
   Persistent<FrameFetchContext> fetchContext;
 
-  Persistent<StubFrameLoaderClientWithParent> childClient;
+  Persistent<StubLocalFrameClientWithParent> childClient;
   Persistent<LocalFrame> childFrame;
   Persistent<Document> childDocument;
   Persistent<DummyFrameOwner> owner;
@@ -194,7 +194,7 @@
   void SetUp() override {
     url = KURL(KURL(), "https://example.test/foo");
     mainResourceUrl = KURL(KURL(), "https://www.example.test");
-    client = new testing::NiceMock<MockFrameLoaderClient>();
+    client = new testing::NiceMock<MockLocalFrameClient>();
     dummyPageHolder =
         DummyPageHolder::create(IntSize(500, 500), nullptr, client);
     dummyPageHolder->page().setDeviceScaleFactor(1.0);
@@ -209,7 +209,7 @@
   KURL url;
   KURL mainResourceUrl;
 
-  Persistent<testing::NiceMock<MockFrameLoaderClient>> client;
+  Persistent<testing::NiceMock<MockLocalFrameClient>> client;
 };
 
 class FrameFetchContextModifyRequestTest : public FrameFetchContextTest {
diff --git a/third_party/WebKit/Source/core/loader/MixedContentCheckerTest.cpp b/third_party/WebKit/Source/core/loader/MixedContentCheckerTest.cpp
index 859a79c..333530a 100644
--- a/third_party/WebKit/Source/core/loader/MixedContentCheckerTest.cpp
+++ b/third_party/WebKit/Source/core/loader/MixedContentCheckerTest.cpp
@@ -103,9 +103,9 @@
 
 namespace {
 
-class MockFrameLoaderClient : public EmptyFrameLoaderClient {
+class MockLocalFrameClient : public EmptyLocalFrameClient {
  public:
-  MockFrameLoaderClient() : EmptyFrameLoaderClient() {}
+  MockLocalFrameClient() : EmptyLocalFrameClient() {}
   MOCK_METHOD1(didDisplayContentWithCertificateErrors, void(const KURL&));
   MOCK_METHOD1(didRunContentWithCertificateErrors, void(const KURL&));
 };
@@ -113,7 +113,7 @@
 }  // namespace
 
 TEST(MixedContentCheckerTest, HandleCertificateError) {
-  MockFrameLoaderClient* client = new MockFrameLoaderClient;
+  MockLocalFrameClient* client = new MockLocalFrameClient;
   std::unique_ptr<DummyPageHolder> dummyPageHolder =
       DummyPageHolder::create(IntSize(1, 1), nullptr, client);
 
diff --git a/third_party/WebKit/Source/core/loader/PingLoaderTest.cpp b/third_party/WebKit/Source/core/loader/PingLoaderTest.cpp
index c5128a4..106ade6 100644
--- a/third_party/WebKit/Source/core/loader/PingLoaderTest.cpp
+++ b/third_party/WebKit/Source/core/loader/PingLoaderTest.cpp
@@ -20,7 +20,7 @@
 
 namespace {
 
-class PingFrameLoaderClient : public EmptyFrameLoaderClient {
+class PingFrameLoaderClient : public EmptyLocalFrameClient {
  public:
   void dispatchWillSendRequest(ResourceRequest& request) override {
     if (request.httpContentType() == "text/ping")
diff --git a/third_party/WebKit/Source/core/loader/ProgressTrackerTest.cpp b/third_party/WebKit/Source/core/loader/ProgressTrackerTest.cpp
index 0672f2d..662d28e 100644
--- a/third_party/WebKit/Source/core/loader/ProgressTrackerTest.cpp
+++ b/third_party/WebKit/Source/core/loader/ProgressTrackerTest.cpp
@@ -12,7 +12,7 @@
 
 namespace blink {
 
-class ProgressClient : public EmptyFrameLoaderClient {
+class ProgressClient : public EmptyLocalFrameClient {
  public:
   ProgressClient() : m_lastProgress(0.0) {}
 
diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoaderClient.h b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoaderClient.h
new file mode 100644
index 0000000..b992048c
--- /dev/null
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleScriptLoaderClient.h
@@ -0,0 +1,31 @@
+// 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 ModuleScriptLoaderClient_h
+#define ModuleScriptLoaderClient_h
+
+#include "platform/heap/Handle.h"
+
+namespace blink {
+
+class ModuleScript;
+
+// A ModuleScriptLoaderClient is notified when a single module script load is
+// complete.
+// Note: Its corresponding module map entry is typically not yet created at the
+// time of callback.
+class ModuleScriptLoaderClient : public GarbageCollectedMixin {
+ public:
+  virtual ~ModuleScriptLoaderClient(){};
+
+ private:
+  friend class ModuleScriptLoader;
+  friend class ModuleMapTestModulator;
+
+  virtual void notifyNewSingleModuleFinished(ModuleScript*) = 0;
+};
+
+}  // namespace blink
+
+#endif
diff --git a/third_party/WebKit/Source/core/loader/resource/ScriptResource.cpp b/third_party/WebKit/Source/core/loader/resource/ScriptResource.cpp
index 9e77ec4..d3be9db 100644
--- a/third_party/WebKit/Source/core/loader/resource/ScriptResource.cpp
+++ b/third_party/WebKit/Source/core/loader/resource/ScriptResource.cpp
@@ -83,11 +83,8 @@
       dump->guid(), String(WTF::Partitions::kAllocatedObjectPoolName));
 }
 
-NOINLINE const String& ScriptResource::script() {
-  // For investigating https://crbug.com/692856.
-  CHECK(isLoaded() || (isLoading() && hasRevalidated()));
-  CHECK(isLoaded() || (isLoading() && isCacheValidator()));
-  CHECK(isLoaded());
+const String& ScriptResource::script() {
+  DCHECK(isLoaded());
 
   if (m_script.isNull() && data()) {
     String script = decodedText();
diff --git a/third_party/WebKit/Source/core/page/FrameTree.cpp b/third_party/WebKit/Source/core/page/FrameTree.cpp
index 5f69da38..36ae58dc 100644
--- a/third_party/WebKit/Source/core/page/FrameTree.cpp
+++ b/third_party/WebKit/Source/core/page/FrameTree.cpp
@@ -116,7 +116,7 @@
 
 Frame* FrameTree::top() const {
   // FIXME: top() should never return null, so here are some hacks to deal
-  // with EmptyFrameLoaderClient and cases where the frame is detached
+  // with EmptyLocalFrameClient and cases where the frame is detached
   // already...
   if (!m_thisFrame->client())
     return m_thisFrame;
diff --git a/third_party/WebKit/Source/core/page/PrintContextTest.cpp b/third_party/WebKit/Source/core/page/PrintContextTest.cpp
index 17cad95..26bb950 100644
--- a/third_party/WebKit/Source/core/page/PrintContextTest.cpp
+++ b/third_party/WebKit/Source/core/page/PrintContextTest.cpp
@@ -144,7 +144,7 @@
 class PrintContextFrameTest : public PrintContextTest {
  public:
   PrintContextFrameTest()
-      : PrintContextTest(SingleChildFrameLoaderClient::create()) {}
+      : PrintContextTest(SingleChildLocalFrameClient::create()) {}
 };
 
 #define EXPECT_SKRECT_EQ(expectedX, expectedY, expectedWidth, expectedHeight, \
diff --git a/third_party/WebKit/Source/core/paint/BoxPaintInvalidatorTest.cpp b/third_party/WebKit/Source/core/paint/BoxPaintInvalidatorTest.cpp
index 970035cc..9f398b4b3 100644
--- a/third_party/WebKit/Source/core/paint/BoxPaintInvalidatorTest.cpp
+++ b/third_party/WebKit/Source/core/paint/BoxPaintInvalidatorTest.cpp
@@ -19,7 +19,7 @@
  public:
   BoxPaintInvalidatorTest()
       : ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
 
  protected:
   const RasterInvalidationTracking* getRasterInvalidationTracking() const {
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerClipperTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerClipperTest.cpp
index 4e63d42..ca5eb027 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerClipperTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerClipperTest.cpp
@@ -20,7 +20,7 @@
  public:
   PaintLayerClipperTest()
       : ScopedSlimmingPaintV2ForTest(GetParam()),
-        RenderingTest(EmptyFrameLoaderClient::create()) {}
+        RenderingTest(EmptyLocalFrameClient::create()) {}
 
   void SetUp() override {
     LayoutTestSupport::setMockThemeEnabledForTest(true);
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
index d0cb1b8..216494b 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
@@ -33,7 +33,7 @@
 class PaintLayerScrollableAreaTest : public RenderingTest {
  public:
   PaintLayerScrollableAreaTest()
-      : RenderingTest(EmptyFrameLoaderClient::create()),
+      : RenderingTest(EmptyLocalFrameClient::create()),
         m_chromeClient(new MockChromeClient) {}
 
   ~PaintLayerScrollableAreaTest() {
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
index d952793..91bf169 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
@@ -23,7 +23,7 @@
   PaintLayerTest()
       : ScopedSlimmingPaintV2ForTest(GetParam().first),
         ScopedRootLayerScrollingForTest(GetParam().second),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
 };
 
 SlimmingPaintAndRootLayerScrolling foo[] = {
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.h b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.h
index 6c023f6..ec76253b 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.h
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.h
@@ -24,7 +24,7 @@
   PaintPropertyTreeBuilderTest()
       : ScopedSlimmingPaintV2ForTest(true),
         ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
 
  protected:
   void loadTestData(const char* fileName);
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinterTest.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinterTest.cpp
index 4cf9a15..885d1f1 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinterTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreePrinterTest.cpp
@@ -24,7 +24,7 @@
   PaintPropertyTreePrinterTest()
       : ScopedSlimmingPaintV2ForTest(true),
         ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(SingleChildFrameLoaderClient::create()) {}
+        RenderingTest(SingleChildLocalFrameClient::create()) {}
 
  private:
   void SetUp() override {
diff --git a/third_party/WebKit/Source/core/paint/PrePaintTreeWalkTest.cpp b/third_party/WebKit/Source/core/paint/PrePaintTreeWalkTest.cpp
index dcb708f..7ddfcb4 100644
--- a/third_party/WebKit/Source/core/paint/PrePaintTreeWalkTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PrePaintTreeWalkTest.cpp
@@ -30,7 +30,7 @@
   PrePaintTreeWalkTest()
       : ScopedSlimmingPaintV2ForTest(true),
         ScopedRootLayerScrollingForTest(GetParam()),
-        RenderingTest(EmptyFrameLoaderClient::create()) {}
+        RenderingTest(EmptyLocalFrameClient::create()) {}
 
   const TransformPaintPropertyNode* framePreTranslation() {
     FrameView* frameView = document().view();
diff --git a/third_party/WebKit/Source/core/paint/VideoPainterTest.cpp b/third_party/WebKit/Source/core/paint/VideoPainterTest.cpp
index af01aa16d..1e649de57 100644
--- a/third_party/WebKit/Source/core/paint/VideoPainterTest.cpp
+++ b/third_party/WebKit/Source/core/paint/VideoPainterTest.cpp
@@ -79,7 +79,7 @@
   ReadyState m_readyState = ReadyStateHaveNothing;
 };
 
-class StubFrameLoaderClient : public EmptyFrameLoaderClient {
+class StubLocalFrameClient : public EmptyLocalFrameClient {
  public:
   // FrameLoaderClient
   std::unique_ptr<WebMediaPlayer> createWebMediaPlayer(
@@ -98,12 +98,12 @@
  protected:
   void SetUp() override {
     m_chromeClient = new StubChromeClientForSPv2();
-    m_frameLoaderClient = new StubFrameLoaderClient;
+    m_localFrameClient = new StubLocalFrameClient;
     Page::PageClients clients;
     fillWithEmptyClients(clients);
     clients.chromeClient = m_chromeClient.get();
     m_pageHolder = DummyPageHolder::create(
-        IntSize(800, 600), &clients, m_frameLoaderClient.get(),
+        IntSize(800, 600), &clients, m_localFrameClient.get(),
         [](Settings& settings) {
           settings.setAcceleratedCompositingEnabled(true);
         });
@@ -119,7 +119,7 @@
 
  private:
   Persistent<StubChromeClientForSPv2> m_chromeClient;
-  Persistent<StubFrameLoaderClient> m_frameLoaderClient;
+  Persistent<StubLocalFrameClient> m_localFrameClient;
   std::unique_ptr<DummyPageHolder> m_pageHolder;
 };
 
diff --git a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
index f767fa7..85f1284 100644
--- a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
+++ b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp
@@ -33,6 +33,7 @@
 #include "core/dom/shadow/FlatTreeTraversal.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/layout/LayoutView.h"
 #include "core/layout/svg/LayoutSVGRoot.h"
@@ -579,8 +580,8 @@
     // types.
     EventDispatchForbiddenScope::AllowUserAgentEvents allowUserAgentEvents;
 
-    DEFINE_STATIC_LOCAL(FrameLoaderClient, dummyFrameLoaderClient,
-                        (EmptyFrameLoaderClient::create()));
+    DEFINE_STATIC_LOCAL(LocalFrameClient, dummyLocalFrameClient,
+                        (EmptyLocalFrameClient::create()));
 
     if (m_page) {
       toLocalFrame(m_page->mainFrame())
@@ -634,8 +635,7 @@
     LocalFrame* frame = nullptr;
     {
       TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame");
-      frame =
-          LocalFrame::create(&dummyFrameLoaderClient, &page->frameHost(), 0);
+      frame = LocalFrame::create(&dummyLocalFrameClient, &page->frameHost(), 0);
       frame->setView(FrameView::create(*frame));
       frame->init();
     }
diff --git a/third_party/WebKit/Source/core/testing/DummyModulator.cpp b/third_party/WebKit/Source/core/testing/DummyModulator.cpp
new file mode 100644
index 0000000..0ca4b1b
--- /dev/null
+++ b/third_party/WebKit/Source/core/testing/DummyModulator.cpp
@@ -0,0 +1,31 @@
+// 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 "core/testing/DummyModulator.h"
+
+namespace blink {
+
+DummyModulator::DummyModulator() {}
+
+DummyModulator::~DummyModulator() {}
+
+DEFINE_TRACE(DummyModulator) {}
+
+ScriptModuleResolver* DummyModulator::scriptModuleResolver() {
+  NOTREACHED();
+  return nullptr;
+}
+
+WebTaskRunner* DummyModulator::taskRunner() {
+  NOTREACHED();
+  return nullptr;
+};
+
+void DummyModulator::fetchNewSingleModule(const ModuleScriptFetchRequest&,
+                                          ModuleGraphLevel,
+                                          ModuleScriptLoaderClient*) {
+  NOTREACHED();
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/testing/DummyModulator.h b/third_party/WebKit/Source/core/testing/DummyModulator.h
new file mode 100644
index 0000000..9d72dc1e
--- /dev/null
+++ b/third_party/WebKit/Source/core/testing/DummyModulator.h
@@ -0,0 +1,43 @@
+// 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 DummyModulator_h
+#define DummyModulator_h
+
+#include "core/dom/Modulator.h"
+#include "platform/heap/Handle.h"
+
+namespace blink {
+
+class ModuleScriptLoaderClient;
+class ScriptModuleResolver;
+class WebTaskRunner;
+class ModuleScriptFetchRequest;
+
+// DummyModulator provides empty Modulator interface implementation w/
+// NOTREACHED().
+//
+// DummyModulator is useful for unit-testing.
+// Not all module implementation components require full-blown Modulator
+// implementation. Unit tests can implement a subset of Modulator interface
+// which is exercised from unit-under-test.
+class DummyModulator : public GarbageCollectedFinalized<DummyModulator>,
+                       public Modulator {
+  USING_GARBAGE_COLLECTED_MIXIN(DummyModulator);
+
+ public:
+  DummyModulator();
+  virtual ~DummyModulator();
+  DECLARE_TRACE();
+
+  ScriptModuleResolver* scriptModuleResolver() override;
+  WebTaskRunner* taskRunner() override;
+  void fetchNewSingleModule(const ModuleScriptFetchRequest&,
+                            ModuleGraphLevel,
+                            ModuleScriptLoaderClient*) override;
+};
+
+}  // namespace blink
+
+#endif  // DummyModulator_h
diff --git a/third_party/WebKit/Source/core/testing/DummyPageHolder.cpp b/third_party/WebKit/Source/core/testing/DummyPageHolder.cpp
index 0b06c34e..e0d5903 100644
--- a/third_party/WebKit/Source/core/testing/DummyPageHolder.cpp
+++ b/third_party/WebKit/Source/core/testing/DummyPageHolder.cpp
@@ -78,7 +78,7 @@
 
   m_frameLoaderClient = frameLoaderClient;
   if (!m_frameLoaderClient)
-    m_frameLoaderClient = EmptyFrameLoaderClient::create();
+    m_frameLoaderClient = EmptyLocalFrameClient::create();
 
   m_frame = LocalFrame::create(m_frameLoaderClient.get(), &m_page->frameHost(),
                                nullptr, interfaceProvider);
diff --git a/third_party/WebKit/Source/core/testing/Internals.cpp b/third_party/WebKit/Source/core/testing/Internals.cpp
index 8449f7f..981251a 100644
--- a/third_party/WebKit/Source/core/testing/Internals.cpp
+++ b/third_party/WebKit/Source/core/testing/Internals.cpp
@@ -67,7 +67,6 @@
 #include "core/editing/markers/DocumentMarker.h"
 #include "core/editing/markers/DocumentMarkerController.h"
 #include "core/editing/serializers/Serialization.h"
-#include "core/editing/spellcheck/IdleSpellCheckCallback.h"
 #include "core/editing/spellcheck/SpellCheckRequester.h"
 #include "core/editing/spellcheck/SpellChecker.h"
 #include "core/frame/EventHandlerRegistry.h"
@@ -210,9 +209,7 @@
 static SpellCheckRequester* spellCheckRequester(Document* document) {
   if (!document || !document->frame())
     return 0;
-  if (!RuntimeEnabledFeatures::idleTimeSpellCheckingEnabled())
-    return &document->frame()->spellChecker().spellCheckRequester();
-  return &document->frame()->idleSpellCheckCallback().spellCheckRequester();
+  return &document->frame()->spellChecker().spellCheckRequester();
 }
 
 static ScrollableArea* scrollableAreaForNode(Node* node) {
diff --git a/third_party/WebKit/Source/modules/webaudio/BaseAudioContextTest.cpp b/third_party/WebKit/Source/modules/webaudio/BaseAudioContextTest.cpp
index 0dba8a3..c143063 100644
--- a/third_party/WebKit/Source/modules/webaudio/BaseAudioContextTest.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/BaseAudioContextTest.cpp
@@ -27,7 +27,7 @@
 
 const char* const kCrossOriginMetric = "WebAudio.Autoplay.CrossOrigin";
 
-class MockCrossOriginFrameLoaderClient final : public EmptyFrameLoaderClient {
+class MockCrossOriginFrameLoaderClient final : public EmptyLocalFrameClient {
  public:
   static MockCrossOriginFrameLoaderClient* create(Frame* parent) {
     return new MockCrossOriginFrameLoaderClient(parent);
@@ -35,7 +35,7 @@
 
   DEFINE_INLINE_VIRTUAL_TRACE() {
     visitor->trace(m_parent);
-    EmptyFrameLoaderClient::trace(visitor);
+    EmptyLocalFrameClient::trace(visitor);
   }
 
   Frame* parent() const override { return m_parent.get(); }
diff --git a/third_party/WebKit/Source/platform/fonts/FontCache.cpp b/third_party/WebKit/Source/platform/fonts/FontCache.cpp
index 9525ac4c..d5a4e3d 100644
--- a/third_party/WebKit/Source/platform/fonts/FontCache.cpp
+++ b/third_party/WebKit/Source/platform/fonts/FontCache.cpp
@@ -441,18 +441,9 @@
   gGeneration++;
 
   HeapVector<Member<FontCacheClient>> clients;
-  size_t numClients = fontCacheClients().size();
-  clients.reserveInitialCapacity(numClients);
-  HeapHashSet<WeakMember<FontCacheClient>>::iterator end =
-      fontCacheClients().end();
-  for (HeapHashSet<WeakMember<FontCacheClient>>::iterator it =
-           fontCacheClients().begin();
-       it != end; ++it)
-    clients.push_back(*it);
-
-  ASSERT(numClients == clients.size());
-  for (size_t i = 0; i < numClients; ++i)
-    clients[i]->fontCacheInvalidated();
+  copyToVector(fontCacheClients(), clients);
+  for (const auto& client : clients)
+    client->fontCacheInvalidated();
 
   purge(ForcePurge);
 }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
index 46294ce..42e0614 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
@@ -568,7 +568,6 @@
   DCHECK(!request.isNull());
   CHECK(!m_isRevalidationStartForbidden);
   m_isRevalidating = true;
-  m_hasRevalidated = true;
   m_resourceRequest = request;
   m_status = ResourceStatus::NotStarted;
 }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/Resource.h b/third_party/WebKit/Source/platform/loader/fetch/Resource.h
index 26e6b974..c17ba26 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/Resource.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/Resource.h
@@ -266,9 +266,6 @@
   bool hasCacheControlNoStoreHeader() const;
   bool mustReloadDueToVaryHeader(const ResourceRequest& newRequest) const;
 
-  // For investigating https://crbug.com/692856.
-  bool hasRevalidated() const { return m_hasRevalidated; }
-
   bool isEligibleForIntegrityCheck(SecurityOrigin*) const;
 
   void setIntegrityMetadata(const IntegrityMetadataSet& metadata) {
@@ -462,12 +459,6 @@
   bool m_needsSynchronousCacheHit;
   bool m_linkPreload;
   bool m_isRevalidating;
-
-  // For investigating https://crbug.com/692856.
-  // Indicates whether at least one revalidation is done for |this|.
-  // Used for assertions only.
-  bool m_hasRevalidated = false;
-
   bool m_isAlive;
   bool m_isAddRemoveClientProhibited;
   bool m_isRevalidationStartForbidden = false;
diff --git a/third_party/WebKit/Source/web/InspectorOverlay.cpp b/third_party/WebKit/Source/web/InspectorOverlay.cpp
index 721586a..b7d9e4c 100644
--- a/third_party/WebKit/Source/web/InspectorOverlay.cpp
+++ b/third_party/WebKit/Source/web/InspectorOverlay.cpp
@@ -28,6 +28,8 @@
 
 #include "web/InspectorOverlay.h"
 
+#include <memory>
+
 #include "bindings/core/v8/ScriptController.h"
 #include "bindings/core/v8/ScriptSourceCode.h"
 #include "bindings/core/v8/V8InspectorOverlayHost.h"
@@ -36,6 +38,7 @@
 #include "core/frame/FrameHost.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/frame/VisualViewport.h"
 #include "core/input/EventHandler.h"
@@ -50,13 +53,12 @@
 #include "platform/graphics/paint/CullRect.h"
 #include "public/platform/Platform.h"
 #include "public/platform/WebData.h"
+#include "v8/include/v8.h"
 #include "web/ChromeClientImpl.h"
 #include "web/PageOverlay.h"
 #include "web/WebInputEventConversion.h"
 #include "web/WebLocalFrameImpl.h"
 #include "wtf/AutoReset.h"
-#include <memory>
-#include <v8.h>
 
 namespace blink {
 
@@ -502,8 +504,8 @@
 
   ScriptForbiddenScope::AllowUserAgentScript allowScript;
 
-  DEFINE_STATIC_LOCAL(FrameLoaderClient, dummyFrameLoaderClient,
-                      (EmptyFrameLoaderClient::create()));
+  DEFINE_STATIC_LOCAL(LocalFrameClient, dummyLocalFrameClient,
+                      (EmptyLocalFrameClient::create()));
   Page::PageClients pageClients;
   fillWithEmptyClients(pageClients);
   DCHECK(!m_overlayChromeClient);
@@ -538,7 +540,7 @@
   // through some non-composited paint function.
   overlaySettings.setAcceleratedCompositingEnabled(false);
 
-  LocalFrame* frame = LocalFrame::create(&dummyFrameLoaderClient,
+  LocalFrame* frame = LocalFrame::create(&dummyLocalFrameClient,
                                          &m_overlayPage->frameHost(), 0);
   frame->setView(FrameView::create(*frame));
   frame->init();
diff --git a/third_party/WebKit/Source/web/WebLeakDetector.cpp b/third_party/WebKit/Source/web/WebLeakDetector.cpp
index 9efed05..ada00b4 100644
--- a/third_party/WebKit/Source/web/WebLeakDetector.cpp
+++ b/third_party/WebKit/Source/web/WebLeakDetector.cpp
@@ -31,7 +31,6 @@
 #include "public/web/WebLeakDetector.h"
 
 #include "bindings/core/v8/V8GCController.h"
-#include "core/editing/spellcheck/IdleSpellCheckCallback.h"
 #include "core/editing/spellcheck/SpellChecker.h"
 #include "core/workers/InProcessWorkerMessagingProxy.h"
 #include "core/workers/WorkerThread.h"
@@ -102,10 +101,7 @@
   // Stop the spellchecker to prevent this.
   if (frame->isWebLocalFrame()) {
     WebLocalFrameImpl* localFrame = toWebLocalFrameImpl(frame);
-    if (RuntimeEnabledFeatures::idleTimeSpellCheckingEnabled())
-      localFrame->frame()->idleSpellCheckCallback().prepareForLeakDetection();
-    else
-      localFrame->frame()->spellChecker().prepareForLeakDetection();
+    localFrame->frame()->spellChecker().prepareForLeakDetection();
   }
 
   // FIXME: HTML5 Notification should be closed because notification affects the
diff --git a/third_party/WebKit/Source/web/WebPagePopupImpl.cpp b/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
index 3da1db0c..148cd0e 100644
--- a/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
+++ b/third_party/WebKit/Source/web/WebPagePopupImpl.cpp
@@ -35,6 +35,7 @@
 #include "core/frame/FrameHost.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/frame/Settings.h"
 #include "core/frame/VisualViewport.h"
 #include "core/input/EventHandler.h"
@@ -305,10 +306,10 @@
       mainSettings.getScrollAnimatorEnabled());
 
   provideContextFeaturesTo(*m_page, WTF::makeUnique<PagePopupFeaturesClient>());
-  DEFINE_STATIC_LOCAL(FrameLoaderClient, emptyFrameLoaderClient,
-                      (EmptyFrameLoaderClient::create()));
+  DEFINE_STATIC_LOCAL(LocalFrameClient, emptyLocalFrameClient,
+                      (EmptyLocalFrameClient::create()));
   LocalFrame* frame =
-      LocalFrame::create(&emptyFrameLoaderClient, &m_page->frameHost(), 0);
+      LocalFrame::create(&emptyLocalFrameClient, &m_page->frameHost(), 0);
   frame->setPagePopupOwner(m_popupClient->ownerElement());
   frame->setView(FrameView::create(*frame));
   frame->init();
diff --git a/tools/grit/grit/format/resource_map.py b/tools/grit/grit/format/resource_map.py
index e5fdd16..aca302b7 100755
--- a/tools/grit/grit/format/resource_map.py
+++ b/tools/grit/grit/format/resource_map.py
@@ -56,7 +56,7 @@
 #ifndef GRIT_RESOURCE_MAP_STRUCT_
 #define GRIT_RESOURCE_MAP_STRUCT_
 struct GritResourceMap {
-  const char* const name;
+  const char* name;
   int value;
 };
 #endif // GRIT_RESOURCE_MAP_STRUCT_
diff --git a/tools/grit/grit/format/resource_map_unittest.py b/tools/grit/grit/format/resource_map_unittest.py
index ecc997a..31d481e 100755
--- a/tools/grit/grit/format/resource_map_unittest.py
+++ b/tools/grit/grit/format/resource_map_unittest.py
@@ -58,7 +58,7 @@
 #ifndef GRIT_RESOURCE_MAP_STRUCT_
 #define GRIT_RESOURCE_MAP_STRUCT_
 struct GritResourceMap {
-  const char* const name;
+  const char* name;
   int value;
 };
 #endif // GRIT_RESOURCE_MAP_STRUCT_
@@ -150,7 +150,7 @@
 #ifndef GRIT_RESOURCE_MAP_STRUCT_
 #define GRIT_RESOURCE_MAP_STRUCT_
 struct GritResourceMap {
-  const char* const name;
+  const char* name;
   int value;
 };
 #endif // GRIT_RESOURCE_MAP_STRUCT_
@@ -235,7 +235,7 @@
 #ifndef GRIT_RESOURCE_MAP_STRUCT_
 #define GRIT_RESOURCE_MAP_STRUCT_
 struct GritResourceMap {
-  const char* const name;
+  const char* name;
   int value;
 };
 #endif // GRIT_RESOURCE_MAP_STRUCT_
@@ -313,7 +313,7 @@
 #ifndef GRIT_RESOURCE_MAP_STRUCT_
 #define GRIT_RESOURCE_MAP_STRUCT_
 struct GritResourceMap {
-  const char* const name;
+  const char* name;
   int value;
 };
 #endif // GRIT_RESOURCE_MAP_STRUCT_
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 18e186e..102d9ce 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -4983,6 +4983,9 @@
 </histogram>
 
 <histogram name="BlinkGC.TimeForThreadLocalWeakProcessing" units="ms">
+  <obsolete>
+    As of 02/2017, the code for thread-local weak processing was removed.
+  </obsolete>
   <owner>haraken@chromium.org</owner>
   <summary>Duration of time taken to run thread-local weak processing.</summary>
 </histogram>