diff --git a/.gitignore b/.gitignore
index d9ea400..81c2a0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -419,8 +419,6 @@
 /third_party/pdfium
 /third_party/pefile
 /third_party/perl
-/third_party/platformsdk_win7
-/third_party/platformsdk_win8
 /third_party/ppapi
 /third_party/psyco_win32
 /third_party/pthreads-win32
diff --git a/DEPS b/DEPS
index eb98fa5..46fd1e15 100644
--- a/DEPS
+++ b/DEPS
@@ -40,7 +40,7 @@
   # 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': 'ac1f09d53bfe8b99ae7ac82b54e3911258b07b6a',
+  'skia_revision': '08d57e6ae6510a7724119ab548485b4fcbd0a48d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '0ef7ba086f9e48100f9caebd0a52de478f0ada0a',
+  'pdfium_revision': '48f776f7e801d719683b251dc21ee8c0e3250d90',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -228,7 +228,7 @@
     Var('chromium_git') + '/native_client/src/third_party/scons-2.0.1.git' + '@' + '1c1550e17fc26355d08627fbdec13d8291227067',
 
   'src/third_party/webrtc':
-    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + 'af5a0a05e5bfc8bf22c0be334f4e8c0a9f600875', # commit position 15911
+    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + '5445b0716f1a96eb11cc3745093c058dffd53227', # commit position 15919
 
   'src/third_party/openmax_dl':
     Var('chromium_git') + '/external/webrtc/deps/third_party/openmax.git' + '@' +  Var('openmax_dl_revision'),
diff --git a/ash/magnifier/partial_magnification_controller.cc b/ash/magnifier/partial_magnification_controller.cc
index 260d7c3..a9184eb8 100644
--- a/ash/magnifier/partial_magnification_controller.cc
+++ b/ash/magnifier/partial_magnification_controller.cc
@@ -13,6 +13,7 @@
 #include "ui/compositor/paint_recorder.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
+#include "ui/gfx/shadow_value.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
diff --git a/base/metrics/histogram_base.cc b/base/metrics/histogram_base.cc
index 750f048..671cad242 100644
--- a/base/metrics/histogram_base.cc
+++ b/base/metrics/histogram_base.cc
@@ -125,7 +125,9 @@
 // static
 void HistogramBase::EnableActivityReportHistogram(
     const std::string& process_type) {
-  DCHECK(!report_histogram_);
+  if (report_histogram_)
+    return;
+
   size_t existing = StatisticsRecorder::GetHistogramCount();
   if (existing != 0) {
     DVLOG(1) << existing
diff --git a/base/task_scheduler/post_task.cc b/base/task_scheduler/post_task.cc
index 737a219c..8c3e941 100644
--- a/base/task_scheduler/post_task.cc
+++ b/base/task_scheduler/post_task.cc
@@ -30,7 +30,13 @@
 }  // namespace
 
 void PostTask(const tracked_objects::Location& from_here, const Closure& task) {
-  PostTaskWithTraits(from_here, TaskTraits(), task);
+  PostDelayedTask(from_here, task, TimeDelta());
+}
+
+void PostDelayedTask(const tracked_objects::Location& from_here,
+                     const Closure& task,
+                     TimeDelta delay) {
+  PostDelayedTaskWithTraits(from_here, TaskTraits(), task, delay);
 }
 
 void PostTaskAndReply(const tracked_objects::Location& from_here,
@@ -42,7 +48,15 @@
 void PostTaskWithTraits(const tracked_objects::Location& from_here,
                         const TaskTraits& traits,
                         const Closure& task) {
-  TaskScheduler::GetInstance()->PostTaskWithTraits(from_here, traits, task);
+  PostDelayedTaskWithTraits(from_here, traits, task, TimeDelta());
+}
+
+void PostDelayedTaskWithTraits(const tracked_objects::Location& from_here,
+                               const TaskTraits& traits,
+                               const Closure& task,
+                               TimeDelta delay) {
+  TaskScheduler::GetInstance()->PostDelayedTaskWithTraits(from_here, traits,
+                                                          task, delay);
 }
 
 void PostTaskWithTraitsAndReply(const tracked_objects::Location& from_here,
diff --git a/base/task_scheduler/post_task.h b/base/task_scheduler/post_task.h
index 2087e06..ee1de7fb 100644
--- a/base/task_scheduler/post_task.h
+++ b/base/task_scheduler/post_task.h
@@ -15,6 +15,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/task_runner.h"
 #include "base/task_scheduler/task_traits.h"
+#include "base/time/time.h"
 
 namespace base {
 
@@ -58,12 +59,25 @@
 // If those loose requirements are sufficient for your task, use
 // PostTask[AndReply], otherwise override these with explicit traits via
 // PostTaskWithTraits[AndReply].
+//
+// Tasks posted to TaskScheduler with a delay may be coalesced (i.e. delays may
+// be adjusted to reduce the number of wakeups and hence power consumption).
 
 // Posts |task| to the TaskScheduler. Calling this is equivalent to calling
 // PostTaskWithTraits with plain TaskTraits.
 BASE_EXPORT void PostTask(const tracked_objects::Location& from_here,
                           const Closure& task);
 
+// Posts |task| to the TaskScheduler. |task| will not run before |delay|
+// expires. Calling this is equivalent to calling PostDelayedTaskWithTraits with
+// plain TaskTraits.
+//
+// Use PostDelayedTaskWithTraits to specify a BACKGROUND priority if the task
+// doesn't have to run as soon as |delay| expires.
+BASE_EXPORT void PostDelayedTask(const tracked_objects::Location& from_here,
+                                 const Closure& task,
+                                 TimeDelta delay);
+
 // Posts |task| to the TaskScheduler and posts |reply| on the caller's execution
 // context (i.e. same sequence or thread and same TaskTraits if applicable) when
 // |task| completes. Calling this is equivalent to calling
@@ -90,6 +104,17 @@
                                     const TaskTraits& traits,
                                     const Closure& task);
 
+// Posts |task| with specific |traits| to the TaskScheduler. |task| will not run
+// before |delay| expires.
+//
+// Specify a BACKGROUND priority via |traits| if the task doesn't have to run as
+// soon as |delay| expires.
+BASE_EXPORT void PostDelayedTaskWithTraits(
+    const tracked_objects::Location& from_here,
+    const TaskTraits& traits,
+    const Closure& task,
+    TimeDelta delay);
+
 // Posts |task| with specific |traits| to the TaskScheduler and posts |reply| on
 // the caller's execution context (i.e. same sequence or thread and same
 // TaskTraits if applicable) when |task| completes. Can only be called when
@@ -118,10 +143,6 @@
            Owned(result)));
 }
 
-// Delayed tasks posted to TaskRunners returned by the functions below may be
-// coalesced (i.e. delays may be adjusted to reduce the number of wakeups and
-// hence power consumption).
-
 // Returns a TaskRunner whose PostTask invocations result in scheduling tasks
 // using |traits|. Tasks may run in any order and in parallel.
 BASE_EXPORT scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
diff --git a/base/task_scheduler/task_scheduler.h b/base/task_scheduler/task_scheduler.h
index d4084c6f..5d9344b 100644
--- a/base/task_scheduler/task_scheduler.h
+++ b/base/task_scheduler/task_scheduler.h
@@ -15,6 +15,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/task_runner.h"
 #include "base/task_scheduler/task_traits.h"
+#include "base/time/time.h"
 
 namespace tracked_objects {
 class Location;
@@ -39,11 +40,13 @@
 
   virtual ~TaskScheduler() = default;
 
-  // Posts |task| with specific |traits|.
+  // Posts |task| with a |delay| and specific |traits|. |delay| can be zero.
   // For one off tasks that don't require a TaskRunner.
-  virtual void PostTaskWithTraits(const tracked_objects::Location& from_here,
-                                  const TaskTraits& traits,
-                                  const Closure& task) = 0;
+  virtual void PostDelayedTaskWithTraits(
+      const tracked_objects::Location& from_here,
+      const TaskTraits& traits,
+      const Closure& task,
+      TimeDelta delay) = 0;
 
   // Returns a TaskRunner whose PostTask invocations result in scheduling tasks
   // using |traits|. Tasks may run in any order and in parallel.
diff --git a/base/task_scheduler/task_scheduler_impl.cc b/base/task_scheduler/task_scheduler_impl.cc
index 701616e3..827caae 100644
--- a/base/task_scheduler/task_scheduler_impl.cc
+++ b/base/task_scheduler/task_scheduler_impl.cc
@@ -14,7 +14,6 @@
 #include "base/task_scheduler/sequence_sort_key.h"
 #include "base/task_scheduler/task.h"
 #include "base/task_scheduler/task_tracker.h"
-#include "base/time/time.h"
 #include "build/build_config.h"
 
 #if defined(OS_POSIX) && !defined(OS_NACL_SFI)
@@ -41,13 +40,14 @@
 #endif
 }
 
-void TaskSchedulerImpl::PostTaskWithTraits(
+void TaskSchedulerImpl::PostDelayedTaskWithTraits(
     const tracked_objects::Location& from_here,
     const TaskTraits& traits,
-    const Closure& task) {
+    const Closure& task,
+    TimeDelta delay) {
   // Post |task| as part of a one-off single-task Sequence.
   GetWorkerPoolForTraits(traits)->PostTaskWithSequence(
-      MakeUnique<Task>(from_here, task, traits, TimeDelta()),
+      MakeUnique<Task>(from_here, task, traits, delay),
       make_scoped_refptr(new Sequence), nullptr);
 }
 
diff --git a/base/task_scheduler/task_scheduler_impl.h b/base/task_scheduler/task_scheduler_impl.h
index 0e03d52..3e6cfdb 100644
--- a/base/task_scheduler/task_scheduler_impl.h
+++ b/base/task_scheduler/task_scheduler_impl.h
@@ -50,9 +50,10 @@
   ~TaskSchedulerImpl() override;
 
   // TaskScheduler:
-  void PostTaskWithTraits(const tracked_objects::Location& from_here,
-                          const TaskTraits& traits,
-                          const Closure& task) override;
+  void PostDelayedTaskWithTraits(const tracked_objects::Location& from_here,
+                                 const TaskTraits& traits,
+                                 const Closure& task,
+                                 TimeDelta delay) override;
   scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
       const TaskTraits& traits) override;
   scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunnerWithTraits(
diff --git a/base/task_scheduler/task_scheduler_impl_unittest.cc b/base/task_scheduler/task_scheduler_impl_unittest.cc
index 8e22b69..cda0606 100644
--- a/base/task_scheduler/task_scheduler_impl_unittest.cc
+++ b/base/task_scheduler/task_scheduler_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/task_scheduler/scheduler_worker_pool_params.h"
 #include "base/task_scheduler/task_traits.h"
 #include "base/task_scheduler/test_task_factory.h"
+#include "base/test/test_timeouts.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/simple_thread.h"
 #include "base/threading/thread.h"
@@ -53,7 +54,7 @@
 // Verify that the current thread priority and I/O restrictions are appropriate
 // to run a Task with |traits|.
 // Note: ExecutionMode is verified inside TestTaskFactory.
-void VerifyTaskEnvironement(const TaskTraits& traits) {
+void VerifyTaskEnvironment(const TaskTraits& traits) {
   const bool supports_background_priority =
       Lock::HandlesMultipleThreadPriorities() &&
       PlatformThread::CanIncreaseCurrentThreadPriority();
@@ -81,10 +82,19 @@
             current_thread_name.find("Blocking") != std::string::npos);
 }
 
-void VerifyTaskEnvironementAndSignalEvent(const TaskTraits& traits,
-                                          WaitableEvent* event) {
+void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+                                         WaitableEvent* event) {
   DCHECK(event);
-  VerifyTaskEnvironement(traits);
+  VerifyTaskEnvironment(traits);
+  event->Signal();
+}
+
+void VerifyTimeAndTaskEnvironmentAndSignalEvent(const TaskTraits& traits,
+                                                TimeTicks expected_time,
+                                                WaitableEvent* event) {
+  DCHECK(event);
+  EXPECT_LE(expected_time, TimeTicks::Now());
+  VerifyTaskEnvironment(traits);
   event->Signal();
 }
 
@@ -127,7 +137,7 @@
     const size_t kNumTasksPerThread = 150;
     for (size_t i = 0; i < kNumTasksPerThread; ++i) {
       factory_.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                        Bind(&VerifyTaskEnvironement, traits_));
+                        Bind(&VerifyTaskEnvironment, traits_));
     }
   }
 
@@ -220,16 +230,33 @@
 
 }  // namespace
 
-// Verifies that a Task posted via PostTaskWithTraits with parameterized
-// TaskTraits runs on a thread with the expected priority and I/O restrictions.
-// The ExecutionMode parameter is ignored by this test.
-TEST_P(TaskSchedulerImplTest, PostTaskWithTraits) {
+// Verifies that a Task posted via PostDelayedTaskWithTraits with parameterized
+// TaskTraits and no delay runs on a thread with the expected priority and I/O
+// restrictions. The ExecutionMode parameter is ignored by this test.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsNoDelay) {
   WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
                          WaitableEvent::InitialState::NOT_SIGNALED);
-  scheduler_->PostTaskWithTraits(
+  scheduler_->PostDelayedTaskWithTraits(
       FROM_HERE, GetParam().traits,
-      Bind(&VerifyTaskEnvironementAndSignalEvent, GetParam().traits,
-           Unretained(&task_ran)));
+      Bind(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits,
+           Unretained(&task_ran)),
+      TimeDelta());
+  task_ran.Wait();
+}
+
+// Verifies that a Task posted via PostDelayedTaskWithTraits with parameterized
+// TaskTraits and a non-zero delay runs on a thread with the expected priority
+// and I/O restrictions after the delay expires. The ExecutionMode parameter is
+// ignored by this test.
+TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsWithDelay) {
+  WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+                         WaitableEvent::InitialState::NOT_SIGNALED);
+  scheduler_->PostDelayedTaskWithTraits(
+      FROM_HERE, GetParam().traits,
+      Bind(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits,
+           TimeTicks::Now() + TestTimeouts::tiny_timeout(),
+           Unretained(&task_ran)),
+      TestTimeouts::tiny_timeout());
   task_ran.Wait();
 }
 
@@ -246,7 +273,7 @@
   const size_t kNumTasksPerTest = 150;
   for (size_t i = 0; i < kNumTasksPerTest; ++i) {
     factory.PostTask(test::TestTaskFactory::PostNestedTask::NO,
-                     Bind(&VerifyTaskEnvironement, GetParam().traits));
+                     Bind(&VerifyTaskEnvironment, GetParam().traits));
   }
 
   factory.WaitForAllTasksToRun();
diff --git a/base/test/scoped_task_scheduler.cc b/base/test/scoped_task_scheduler.cc
index 19ee891..7858fc4 100644
--- a/base/test/scoped_task_scheduler.cc
+++ b/base/test/scoped_task_scheduler.cc
@@ -40,9 +40,10 @@
   ~TestTaskScheduler() override;
 
   // TaskScheduler:
-  void PostTaskWithTraits(const tracked_objects::Location& from_here,
-                          const TaskTraits& traits,
-                          const Closure& task) override;
+  void PostDelayedTaskWithTraits(const tracked_objects::Location& from_here,
+                                 const TaskTraits& traits,
+                                 const Closure& task,
+                                 TimeDelta delay) override;
   scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
       const TaskTraits& traits) override;
   scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunnerWithTraits(
@@ -124,11 +125,12 @@
   RunLoop().RunUntilIdle();
 }
 
-void TestTaskScheduler::PostTaskWithTraits(
+void TestTaskScheduler::PostDelayedTaskWithTraits(
     const tracked_objects::Location& from_here,
     const TaskTraits& traits,
-    const Closure& task) {
-  CreateTaskRunnerWithTraits(traits)->PostTask(from_here, task);
+    const Closure& task,
+    TimeDelta delay) {
+  CreateTaskRunnerWithTraits(traits)->PostDelayedTask(from_here, task, delay);
 }
 
 scoped_refptr<TaskRunner> TestTaskScheduler::CreateTaskRunnerWithTraits(
diff --git a/cc/layers/render_surface_impl.cc b/cc/layers/render_surface_impl.cc
index 9c980a2..a92e21b 100644
--- a/cc/layers/render_surface_impl.cc
+++ b/cc/layers/render_surface_impl.cc
@@ -35,6 +35,7 @@
     : owning_layer_(owning_layer),
       layer_tree_impl_(owning_layer->layer_tree_impl()),
       stable_effect_id_(owning_layer->id()),
+      effect_tree_index_(EffectTree::kInvalidNodeId),
       surface_property_changed_(false),
       ancestor_property_changed_(false),
       contributes_to_drawn_surface_(false),
@@ -159,8 +160,10 @@
 }
 
 int RenderSurfaceImpl::EffectTreeIndex() const {
-  return layer_tree_impl_->property_trees()
-      ->effect_id_to_index_map[stable_effect_id_];
+  DCHECK_EQ(effect_tree_index_,
+            layer_tree_impl_->property_trees()
+                ->effect_id_to_index_map[stable_effect_id_]);
+  return effect_tree_index_;
 }
 
 const EffectNode* RenderSurfaceImpl::OwningEffectNode() const {
diff --git a/cc/layers/render_surface_impl.h b/cc/layers/render_surface_impl.h
index 80c2c15..6f024da9 100644
--- a/cc/layers/render_surface_impl.h
+++ b/cc/layers/render_surface_impl.h
@@ -151,6 +151,8 @@
 
   int TransformTreeIndex() const;
   int ClipTreeIndex() const;
+
+  void set_effect_tree_index(int index) { effect_tree_index_ = index; }
   int EffectTreeIndex() const;
 
  private:
@@ -163,6 +165,7 @@
 
   LayerTreeImpl* layer_tree_impl_;
   int stable_effect_id_;
+  int effect_tree_index_;
 
   // Container for properties that render surfaces need to compute before they
   // can be drawn.
diff --git a/cc/layers/render_surface_unittest.cc b/cc/layers/render_surface_unittest.cc
index 44fc5bb..511108d 100644
--- a/cc/layers/render_surface_unittest.cc
+++ b/cc/layers/render_surface_unittest.cc
@@ -39,16 +39,23 @@
 
   FakeImplTaskRunnerProvider task_runner_provider;
   TestTaskGraphRunner task_graph_runner;
+  std::unique_ptr<CompositorFrameSink> compositor_frame_sink =
+      FakeCompositorFrameSink::Create3d();
   FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
   std::unique_ptr<LayerImpl> owning_layer =
       LayerImpl::Create(host_impl.active_tree(), 1);
-  owning_layer->SetHasRenderSurface(true);
-  ASSERT_TRUE(owning_layer->render_surface());
-  RenderSurfaceImpl* render_surface = owning_layer->render_surface();
+  owning_layer->test_properties()->force_render_surface = true;
   gfx::Rect test_rect(3, 4, 5, 6);
   host_impl.active_tree()->ResetAllChangeTracking();
   host_impl.active_tree()->SetRootLayerForTesting(std::move(owning_layer));
-  host_impl.active_tree()->BuildPropertyTreesForTesting();
+  host_impl.SetVisible(true);
+  host_impl.InitializeRenderer(compositor_frame_sink.get());
+  host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  host_impl.active_tree()->UpdateDrawProperties(false /* update_lcd_text */);
+
+  RenderSurfaceImpl* render_surface =
+      host_impl.active_tree()->root_layer_for_testing()->render_surface();
+  ASSERT_TRUE(render_surface);
 
   // Currently, the content_rect, clip_rect, and
   // owning_layer->layerPropertyChanged() are the only sources of change.
@@ -158,19 +165,28 @@
 TEST(RenderSurfaceTest, SanityCheckSurfaceCreatesCorrectRenderPass) {
   FakeImplTaskRunnerProvider task_runner_provider;
   TestTaskGraphRunner task_graph_runner;
+  std::unique_ptr<CompositorFrameSink> compositor_frame_sink =
+      FakeCompositorFrameSink::Create3d();
   FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
   std::unique_ptr<LayerImpl> root_layer =
       LayerImpl::Create(host_impl.active_tree(), 1);
 
+  int owning_layer_id = 2;
   std::unique_ptr<LayerImpl> owning_layer =
-      LayerImpl::Create(host_impl.active_tree(), 2);
-  owning_layer->SetHasRenderSurface(true);
-  ASSERT_TRUE(owning_layer->render_surface());
-  RenderSurfaceImpl* render_surface = owning_layer->render_surface();
+      LayerImpl::Create(host_impl.active_tree(), owning_layer_id);
+  owning_layer->test_properties()->force_render_surface = true;
 
   root_layer->test_properties()->AddChild(std::move(owning_layer));
   host_impl.active_tree()->SetRootLayerForTesting(std::move(root_layer));
+  host_impl.SetVisible(true);
+  host_impl.InitializeRenderer(compositor_frame_sink.get());
   host_impl.active_tree()->BuildLayerListAndPropertyTreesForTesting();
+  host_impl.active_tree()->UpdateDrawProperties(false /* update_lcd_text */);
+
+  ASSERT_TRUE(
+      host_impl.active_tree()->LayerById(owning_layer_id)->render_surface());
+  RenderSurfaceImpl* render_surface =
+      host_impl.active_tree()->LayerById(owning_layer_id)->render_surface();
 
   gfx::Rect content_rect(0, 0, 50, 50);
   gfx::Transform origin;
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index 80f3b09..7a216f0 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -1072,8 +1072,11 @@
                                 can_render_to_separate_surface, layer);
     EffectNode* node =
         property_trees->effect_tree.Node(layer->effect_tree_index());
-    if (node->owning_layer_id == layer->id())
+    if (node->owning_layer_id == layer->id()) {
       node->render_surface = layer->render_surface();
+      if (node->render_surface)
+        node->render_surface->set_effect_tree_index(node->id);
+    }
 #if DCHECK_IS_ON()
     if (can_render_to_separate_surface)
       ValidateRenderSurfaceForLayer(layer);
diff --git a/cc/trees/layer_tree_host_in_process.cc b/cc/trees/layer_tree_host_in_process.cc
index aac493b0..1f0eab1 100644
--- a/cc/trees/layer_tree_host_in_process.cc
+++ b/cc/trees/layer_tree_host_in_process.cc
@@ -600,8 +600,10 @@
 }
 
 bool LayerTreeHostInProcess::UpdateLayers() {
-  if (!layer_tree_->root_layer())
+  if (!layer_tree_->root_layer()) {
+    layer_tree_->property_trees()->clear();
     return false;
+  }
   DCHECK(!layer_tree_->root_layer()->parent());
   base::ElapsedTimer timer;
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 89a082df9..5e58148 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -5616,6 +5616,82 @@
 // This test blocks activation which is not supported for single thread mode.
 MULTI_THREAD_BLOCKNOTIFY_TEST_F(LayerTreeHostTestActivateOnInvisible);
 
+class LayerTreeHostTestRenderSurfaceEffectTreeIndex : public LayerTreeHostTest {
+ public:
+  LayerTreeHostTestRenderSurfaceEffectTreeIndex() = default;
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  void SetupTree() override {
+    root_ = Layer::Create();
+    child_ = Layer::Create();
+    grand_child_ = Layer::Create();
+
+    layer_tree()->SetRootLayer(root_);
+    root_->AddChild(child_);
+    child_->AddChild(grand_child_);
+
+    root_->SetBounds(gfx::Size(50, 50));
+    child_->SetBounds(gfx::Size(50, 50));
+    grand_child_->SetBounds(gfx::Size(50, 50));
+    child_->SetForceRenderSurfaceForTesting(true);
+    grand_child_->SetForceRenderSurfaceForTesting(true);
+
+    LayerTreeHostTest::SetupTree();
+  }
+
+  void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override {
+    if (host_impl->sync_tree()->source_frame_number() >= 1) {
+      LayerImpl* grand_child_impl =
+          host_impl->sync_tree()->LayerById(grand_child_->id());
+      EXPECT_EQ(grand_child_impl->effect_tree_index(),
+                grand_child_impl->render_surface()->EffectTreeIndex());
+    }
+  }
+
+  void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override {
+    LayerImpl* grand_child_impl =
+        host_impl->active_tree()->LayerById(grand_child_->id());
+    switch (host_impl->active_tree()->source_frame_number()) {
+      case 0:
+        PostSetNeedsCommitToMainThread();
+        break;
+      case 1:
+      case 2:
+        EXPECT_EQ(grand_child_impl->effect_tree_index(),
+                  grand_child_impl->render_surface()->EffectTreeIndex());
+        PostSetNeedsCommitToMainThread();
+        break;
+      case 3:
+        EXPECT_EQ(grand_child_impl->effect_tree_index(),
+                  grand_child_impl->render_surface()->EffectTreeIndex());
+        EndTest();
+    }
+  }
+
+  void DidCommit() override {
+    switch (layer_tree_host()->SourceFrameNumber()) {
+      case 2:
+        // Setting an empty viewport causes draws to get skipped, so the active
+        // tree won't update draw properties.
+        layer_tree()->SetViewportSize(gfx::Size());
+        child_->SetForceRenderSurfaceForTesting(false);
+        break;
+      case 3:
+        layer_tree()->SetViewportSize(root_->bounds());
+    }
+  }
+
+  void AfterTest() override {}
+
+ private:
+  scoped_refptr<Layer> root_;
+  scoped_refptr<Layer> child_;
+  scoped_refptr<Layer> grand_child_;
+};
+
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestRenderSurfaceEffectTreeIndex);
+
 // Do a synchronous composite and assert that the swap promise succeeds.
 class LayerTreeHostTestSynchronousCompositeSwapPromise
     : public LayerTreeHostTest {
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 6045b46..9d4773d 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -379,7 +379,11 @@
 }
 
 void LayerTreeImpl::SetPropertyTrees(PropertyTrees* property_trees) {
+  EffectTree::StableIdRenderSurfaceList stable_id_render_surface_list =
+      property_trees_.effect_tree.CreateStableIdRenderSurfaceList();
   property_trees_ = *property_trees;
+  property_trees_.effect_tree.UpdateRenderSurfaceEffectIds(
+      stable_id_render_surface_list, this);
   property_trees->effect_tree.PushCopyRequestsTo(&property_trees_.effect_tree);
   property_trees_.is_main_thread = false;
   property_trees_.is_active = IsActiveTree();
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index dd449d0..1b0b595 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -991,6 +991,79 @@
   }
 }
 
+EffectTree::StableIdRenderSurfaceList
+EffectTree::CreateStableIdRenderSurfaceList() const {
+  StableIdRenderSurfaceList stable_id_render_surface_list;
+  for (int id = kContentsRootNodeId; id < static_cast<int>(size()); ++id) {
+    const EffectNode* node = Node(id);
+    if (node->render_surface) {
+      stable_id_render_surface_list.push_back(
+          std::make_pair(node->owning_layer_id, node->render_surface));
+    }
+  }
+  std::sort(stable_id_render_surface_list.begin(),
+            stable_id_render_surface_list.end());
+  return stable_id_render_surface_list;
+}
+
+void EffectTree::UpdateRenderSurfaceEffectIds(
+    const EffectTree::StableIdRenderSurfaceList& stable_id_render_surface_list,
+    LayerTreeImpl* layer_tree_impl) {
+  // Make a list of {stable id, node id} pairs for nodes that are supposed to
+  // have surfaces.
+  std::vector<std::pair<int, int>> stable_id_node_id_list;
+  for (int id = kContentsRootNodeId; id < static_cast<int>(size()); ++id) {
+    const EffectNode* node = Node(id);
+    if (node->has_render_surface) {
+      stable_id_node_id_list.push_back(
+          std::make_pair(node->owning_layer_id, node->id));
+    }
+  }
+
+  // Sort by stable id so that we can process the two lists cosequentially.
+  std::sort(stable_id_node_id_list.begin(), stable_id_node_id_list.end());
+
+  auto surface_list_it = stable_id_render_surface_list.begin();
+  auto node_id_list_it = stable_id_node_id_list.begin();
+  while (surface_list_it != stable_id_render_surface_list.end() &&
+         node_id_list_it != stable_id_node_id_list.end()) {
+    if (surface_list_it->first == node_id_list_it->first) {
+      RenderSurfaceImpl* surface = surface_list_it->second;
+      int node_id = node_id_list_it->second;
+      Node(node_id)->render_surface = surface;
+      surface->set_effect_tree_index(node_id);
+      surface_list_it++;
+      node_id_list_it++;
+      continue;
+    }
+
+    if (surface_list_it->first > node_id_list_it->first) {
+      node_id_list_it++;
+      continue;
+    }
+
+    // If we reach here, there's no longer an effect node with stable id
+    // |surface_list_it->first| that has a render surface. If there's no longer
+    // any corresponding layer either, there's nothing more to do since the
+    // surface owned by that layer would have been destroyed when the layer was
+    // destroyed. But if the layer still exists, we need to destroy the surface
+    // since it now has an invalid effect node id.
+    if (LayerImpl* layer_impl =
+            layer_tree_impl->LayerById(surface_list_it->first)) {
+      layer_impl->SetHasRenderSurface(false);
+    }
+    surface_list_it++;
+  }
+
+  while (surface_list_it != stable_id_render_surface_list.end()) {
+    if (LayerImpl* layer_impl =
+            layer_tree_impl->LayerById(surface_list_it->first)) {
+      layer_impl->SetHasRenderSurface(false);
+    }
+    surface_list_it++;
+  }
+}
+
 void TransformTree::UpdateNodeAndAncestorsHaveIntegerTranslations(
     TransformNode* node,
     TransformNode* parent_node) {
@@ -1520,6 +1593,7 @@
   full_tree_damaged = false;
   changed = false;
   non_root_surfaces_enabled = true;
+  sequence_number++;
 
 #if DCHECK_IS_ON()
   PropertyTrees tree;
diff --git a/cc/trees/property_tree.h b/cc/trees/property_tree.h
index 85a8d47..c56126a 100644
--- a/cc/trees/property_tree.h
+++ b/cc/trees/property_tree.h
@@ -30,6 +30,7 @@
 
 class CopyOutputRequest;
 class LayerTreeImpl;
+class RenderSurfaceImpl;
 class ScrollState;
 struct ClipNode;
 struct EffectNode;
@@ -331,6 +332,14 @@
 
   void ResetChangeTracking();
 
+  // A list of pairs of stable id and render surface, sorted by stable id.
+  using StableIdRenderSurfaceList =
+      std::vector<std::pair<int, RenderSurfaceImpl*>>;
+  StableIdRenderSurfaceList CreateStableIdRenderSurfaceList() const;
+  void UpdateRenderSurfaceEffectIds(
+      const StableIdRenderSurfaceList& stable_id_render_surface_list,
+      LayerTreeImpl* layer_tree_impl);
+
  private:
   void UpdateOpacities(EffectNode* node, EffectNode* parent_node);
   void UpdateIsDrawn(EffectNode* node, EffectNode* parent_node);
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index e4e6fc1..7017538 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -1376,8 +1376,6 @@
     return;
   }
 
-  property_trees->sequence_number++;
-
   DataForRecursion<LayerType> data_for_recursion;
   data_for_recursion.property_trees = property_trees;
   data_for_recursion.transform_tree_parent = nullptr;
diff --git a/chrome/android/java/res/menu/custom_tabs_menu.xml b/chrome/android/java/res/menu/custom_tabs_menu.xml
index bc7f5aef..2ae82d5 100644
--- a/chrome/android/java/res/menu/custom_tabs_menu.xml
+++ b/chrome/android/java/res/menu/custom_tabs_menu.xml
@@ -11,6 +11,12 @@
               <item android:id="@+id/forward_menu_id"
                 android:title="@string/accessibility_menu_forward"
                 android:icon="@drawable/btn_forward"/>
+              <item android:id="@+id/bookmark_this_page_id"
+                android:title="@string/accessibility_menu_bookmark"
+                android:icon="@drawable/btn_star"/>
+              <item android:id="@+id/offline_page_id"
+                android:title="@string/download_page"
+                android:icon="@drawable/ic_get_app_white_24dp"/>
               <item android:id="@+id/info_menu_id"
                 android:title="@string/accessibility_menu_info"
                 android:icon="@drawable/btn_info" />
@@ -30,6 +36,17 @@
           </menu>
         </item>
         <!-- Title is intentionally left blank in xml and will be set in java. -->
+        <item android:id="@+id/find_in_page_id"
+            android:title="@string/menu_find_in_page"
+            android:orderInCategory="2" />
+        <item android:id="@+id/add_to_homescreen_id"
+            android:title="@string/menu_add_to_homescreen"
+            android:orderInCategory="2" />
+        <item android:id="@+id/request_desktop_site_id"
+            android:title="@string/menu_request_desktop_site"
+            android:checkable="true"
+            android:icon="?android:attr/listChoiceIndicatorMultiple"
+            android:orderInCategory="2" />
         <item android:id="@+id/open_in_browser_id"
             android:title=""
             android:orderInCategory="2" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index cf426e8..bee5d351 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -1302,9 +1302,9 @@
                 if (!tabToBookmark.isClosing() && tabToBookmark.isInitialized()) {
                     // The BookmarkModel will be destroyed by BookmarkUtils#addOrEditBookmark() when
                     // done.
-                    BookmarkId newBookmarkId =
-                            BookmarkUtils.addOrEditBookmark(bookmarkId, bookmarkModel,
-                                    tabToBookmark, getSnackbarManager(), ChromeActivity.this);
+                    BookmarkId newBookmarkId = BookmarkUtils.addOrEditBookmark(bookmarkId,
+                            bookmarkModel, tabToBookmark, getSnackbarManager(), ChromeActivity.this,
+                            isCustomTab());
                     // If a new bookmark was created, try to save an offline page for it.
                     if (newBookmarkId != null && newBookmarkId.getId() != bookmarkId) {
                         OfflinePageUtils.saveBookmarkOffline(newBookmarkId, tabToBookmark);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
index 6d0529a..63345c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
@@ -179,11 +179,7 @@
             // Hide request desktop site on all chrome:// pages except for the NTP. Check request
             // desktop site if it's activated on this page.
             MenuItem requestItem = menu.findItem(R.id.request_desktop_site_id);
-            requestItem.setVisible(!isChromeScheme || currentTab.isNativePage());
-            requestItem.setChecked(currentTab.getUseDesktopUserAgent());
-            requestItem.setTitleCondensed(requestItem.isChecked()
-                    ? mActivity.getString(R.string.menu_request_desktop_site_on)
-                    : mActivity.getString(R.string.menu_request_desktop_site_off));
+            updateRequestDesktopSiteMenuItem(requestItem, currentTab, isChromeScheme);
 
             // Only display reader mode settings menu option if the current page is in reader mode.
             menu.findItem(R.id.reader_mode_prefs_id)
@@ -292,4 +288,21 @@
             bookmarkMenuItem.setTitleCondensed(null);
         }
     }
+
+    /**
+     * Updates the request desktop site item's visibility
+     *
+     * @param requstMenuItem {@link MenuItem} for request desktop site.
+     * @param currentTab      Current tab being displayed.
+     * @param isChromeScheme  whether the url being displayed starts with chrome:// or
+     * chrome-native://.
+     */
+    protected void updateRequestDesktopSiteMenuItem(
+            MenuItem requstMenuItem, Tab currentTab, boolean isChromeScheme) {
+        requstMenuItem.setVisible(!isChromeScheme || currentTab.isNativePage());
+        requstMenuItem.setChecked(currentTab.getUseDesktopUserAgent());
+        requstMenuItem.setTitleCondensed(requstMenuItem.isChecked()
+                        ? mActivity.getString(R.string.menu_request_desktop_site_on)
+                        : mActivity.getString(R.string.menu_request_desktop_site_off));
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index 9ab1e77..ddfdf26 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -13,6 +13,7 @@
 import android.provider.Browser;
 import android.text.TextUtils;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
@@ -50,11 +51,12 @@
      * @param tab The tab to add or edit a bookmark.
      * @param snackbarManager The SnackbarManager used to show the snackbar.
      * @param activity Current activity.
+     * @param fromCustomTab boolean indicates whether it is called by Custom Tab.
      * @return Bookmark ID of the bookmark. Could be <code>null</code> if bookmark didn't exist
      *   and bookmark model failed to create it.
      */
     public static BookmarkId addOrEditBookmark(long existingBookmarkId, BookmarkModel bookmarkModel,
-            Tab tab, SnackbarManager snackbarManager, Activity activity) {
+            Tab tab, SnackbarManager snackbarManager, Activity activity, boolean fromCustomTab) {
         if (existingBookmarkId != Tab.INVALID_BOOKMARK_ID) {
             BookmarkId bookmarkId = new BookmarkId(existingBookmarkId, BookmarkType.NORMAL);
             startEditActivity(activity, bookmarkId);
@@ -89,8 +91,16 @@
             SnackbarController snackbarController =
                     createSnackbarControllerForEditButton(activity, bookmarkId);
             if (getLastUsedParent(activity) == null) {
-                snackbar = Snackbar.make(activity.getString(R.string.bookmark_page_saved),
-                        snackbarController, Snackbar.TYPE_ACTION, Snackbar.UMA_BOOKMARK_ADDED);
+                if (fromCustomTab) {
+                    String packageLabel = BuildInfo.getPackageLabel(activity);
+                    snackbar = Snackbar.make(
+                            activity.getString(R.string.menu_open_in_product, packageLabel),
+                            snackbarController, Snackbar.TYPE_ACTION, Snackbar.UMA_BOOKMARK_ADDED);
+                } else {
+                    snackbar = Snackbar.make(
+                            activity.getString(R.string.bookmark_page_saved_default),
+                            snackbarController, Snackbar.TYPE_ACTION, Snackbar.UMA_BOOKMARK_ADDED);
+                }
             } else {
                 snackbar = Snackbar.make(folderName, snackbarController, Snackbar.TYPE_ACTION,
                         Snackbar.UMA_BOOKMARK_ADDED)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 7c09524..a478767f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -47,6 +47,7 @@
 import org.chromium.chrome.browser.WarmupManager;
 import org.chromium.chrome.browser.WebContentsFactory;
 import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerDocument;
 import org.chromium.chrome.browser.datausage.DataUseTabUIManager;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
@@ -841,6 +842,20 @@
                 || id == R.id.new_incognito_tab_menu_id || id == R.id.new_tab_menu_id
                 || id == R.id.open_history_menu_id) {
             return true;
+        } else if (id == R.id.bookmark_this_page_id) {
+            addOrEditBookmark(getActivityTab());
+            RecordUserAction.record("MobileMenuAddToBookmarks");
+            return true;
+        } else if (id == R.id.find_in_page_id) {
+            mFindToolbarManager.showToolbar();
+            if (getContextualSearchManager() != null) {
+                getContextualSearchManager().hideContextualSearch(StateChangeReason.UNKNOWN);
+            }
+            if (fromMenu) {
+                RecordUserAction.record("MobileMeanuFindInPage");
+            } else {
+                RecordUserAction.record("MobileShortcutFindInPage");
+            }
         } else if (id == R.id.open_in_browser_id) {
             openCurrentUrlInBrowser(false);
             RecordUserAction.record("CustomTabsMenuOpenInChrome");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java
index a894354..e37baf34 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java
@@ -16,6 +16,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
 import org.chromium.chrome.browser.share.ShareHelper;
@@ -97,6 +98,9 @@
 
             MenuItem iconRow = menu.findItem(R.id.icon_row_menu_id);
             MenuItem openInChromeItem = menu.findItem(R.id.open_in_browser_id);
+            MenuItem bookmarkItem = menu.findItem(R.id.bookmark_this_page_id);
+            MenuItem downloadItem = menu.findItem(R.id.offline_page_id);
+            MenuItem addToHomeScreenItem = menu.findItem(R.id.add_to_homescreen_id);
             if (mIsMediaViewer) {
                 // Most of the menu items don't make sense when viewing media.
                 iconRow.setVisible(false);
@@ -108,10 +112,14 @@
                     openInChromeItem.setTitle(
                             mActivity.getString(R.string.menu_open_in_product_default));
                 }
+                updateBookmarkMenuItem(bookmarkItem, currentTab);
             }
 
             if (!FirstRunStatus.getFirstRunFlowComplete()) {
                 openInChromeItem.setVisible(false);
+                bookmarkItem.setVisible(false);
+                downloadItem.setVisible(false);
+                addToHomeScreenItem.setVisible(false);
             }
 
             // Add custom menu items. Make sure they are only added once.
@@ -122,6 +130,14 @@
                     mItemToIndexMap.put(item, i);
                 }
             }
+
+            // Hide request desktop site on all chrome:// pages except for the NTP. Check request
+            // desktop site if it's activated on this page.
+            MenuItem requestItem = menu.findItem(R.id.request_desktop_site_id);
+            String url = currentTab.getUrl();
+            boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_SCHEME)
+                    || url.startsWith(UrlConstants.CHROME_NATIVE_SCHEME);
+            updateRequestDesktopSiteMenuItem(requestItem, currentTab, isChromeScheme);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
index 206998b..401c1db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
@@ -9,7 +9,9 @@
 import android.content.Context;
 
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.BuildInfo;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBridge;
 import org.chromium.chrome.browser.snackbar.Snackbar;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
@@ -74,9 +76,17 @@
     public void onDownloadSucceeded(
             DownloadInfo downloadInfo, int notificationId, long downloadId, boolean canBeResolved) {
         if (getSnackbarManager() == null) return;
-        Snackbar snackbar = Snackbar.make(
-                mContext.getString(R.string.download_succeeded_message, downloadInfo.getFileName()),
-                this, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_DOWNLOAD_SUCCEEDED);
+        Snackbar snackbar;
+        if (getActivity() instanceof CustomTabActivity) {
+            String packageLabel = BuildInfo.getPackageLabel(getActivity());
+            snackbar = Snackbar.make(mContext.getString(R.string.download_succeeded_message,
+                    downloadInfo.getFileName(), packageLabel),
+                    this, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_DOWNLOAD_SUCCEEDED);
+        } else {
+            snackbar = Snackbar.make(mContext.getString(R.string.download_succeeded_message_default,
+                    downloadInfo.getFileName()),
+                    this, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_DOWNLOAD_SUCCEEDED);
+        }
         // TODO(qinmin): Coalesce snackbars if multiple downloads finish at the same time.
         snackbar.setDuration(SNACKBAR_DURATION_IN_MILLISECONDS).setSingleLine(false);
         ActionDataInfo info = null;
@@ -111,10 +121,17 @@
         getSnackbarManager().showSnackbar(snackbar);
     }
 
+    private Activity getActivity() {
+        if (ApplicationStatus.hasVisibleActivities()) {
+            return ApplicationStatus.getLastTrackedFocusedActivity();
+        } else {
+            return null;
+        }
+    }
+
     public SnackbarManager getSnackbarManager() {
-        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
-        if (activity != null && ApplicationStatus.hasVisibleActivities()
-                && activity instanceof SnackbarManager.SnackbarManageable) {
+        Activity activity = getActivity();
+        if (activity != null && activity instanceof SnackbarManager.SnackbarManageable) {
             return ((SnackbarManager.SnackbarManageable) activity).getSnackbarManager();
         }
         return null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/NativeInitializationController.java b/chrome/android/java/src/org/chromium/chrome/browser/init/NativeInitializationController.java
index fc021528..a4835818 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/NativeInitializationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/NativeInitializationController.java
@@ -93,6 +93,8 @@
     public void startBackgroundTasks(final boolean allocateChildConnection) {
         ThreadUtils.assertOnUiThread();
 
+        // TODO(asvitkine): Consider moving this logic to a singleton, like
+        // ChromeBrowserInitializer.
         if (shouldFetchVariationsSeedBeforeFRE()) {
             Context context = ContextUtils.getApplicationContext();
             Intent initialIntent = mActivityDelegate.getInitialIntent();
@@ -105,6 +107,10 @@
                         new BroadcastReceiver() {
                             @Override
                             public void onReceive(Context context, Intent intent) {
+                                // This check is needed because onReceive() can be called multiple
+                                // times even after having unregistered below if two broadcasts
+                                // arrive in rapid succession.
+                                if (!mWaitingForVariationsFetch) return;
                                 mWaitingForVariationsFetch = false;
                                 manager.unregisterReceiver(this);
                                 signalNativeLibraryLoadedIfReady();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillContact.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillContact.java
index 667d258..8a91399 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillContact.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AutofillContact.java
@@ -19,6 +19,10 @@
 public class AutofillContact extends PaymentOption {
     private final AutofillProfile mProfile;
     private final Context mContext;
+    private int mCompletionStatus;
+    private boolean mRequestName;
+    private boolean mRequestPhone;
+    private boolean mRequestEmail;
     @Nullable private String mPayerName;
     @Nullable private String mPayerPhone;
     @Nullable private String mPayerEmail;
@@ -33,13 +37,20 @@
      * @param email            The email address. If name and phone are empty, this will be the
      *                         primary label.
      * @param completionStatus The completion status of this contact.
+     * @param requestName      Whether the merchant requests a payer name.
+     * @param requestPhone     Whether the merchant requests a payer phone number.
+     * @param requestEmail     Whether the merchant requests a payer email address.
      */
     public AutofillContact(Context context, AutofillProfile profile, @Nullable String name,
             @Nullable String phone, @Nullable String email,
-            @ContactEditor.CompletionStatus int completionStatus) {
+            @ContactEditor.CompletionStatus int completionStatus, boolean requestName,
+            boolean requestPhone, boolean requestEmail) {
         super(profile.getGUID(), null, null, null, null);
         mContext = context;
         mProfile = profile;
+        mRequestName = requestName;
+        mRequestPhone = requestPhone;
+        mRequestEmail = requestEmail;
         mIsEditable = true;
         setContactInfo(profile.getGUID(), name, phone, email);
         updateCompletionStatus(completionStatus);
@@ -50,16 +61,17 @@
         return mPayerName;
     }
 
-    /** @return Email address. Null if the merchant did not request it or data is incomplete. */
-    @Nullable public String getPayerEmail() {
-        return mPayerEmail;
-    }
-
     /** @return Phone number. Null if the merchant did not request it or data is incomplete. */
     @Nullable public String getPayerPhone() {
         return mPayerPhone;
     }
 
+    /** @return Email address. Null if the merchant did not request it or data is incomplete. */
+    @Nullable
+    public String getPayerEmail() {
+        return mPayerEmail;
+    }
+
     /** @return The autofill profile where this contact data lives. */
     public AutofillProfile getProfile() {
         return mProfile;
@@ -87,6 +99,60 @@
         updateCompletionStatus(ContactEditor.COMPLETE);
     }
 
+    /**
+     * Returns whether this contact is equal or a superset of the specified contact considering the
+     * information requested by the merchant.
+     *
+     * @param contact The contact to compare to.
+     * @return Whether this contact is equal to or a superset of the other.
+     */
+    public boolean isEqualOrSupersetOf(AutofillContact contact) {
+        assert contact != null;
+
+        // This contact is not equal to or a superset of the other if for a requested field:
+        // 1- This contact's field is null and the other's is not.
+        // 2- The field values are not equal.
+        if (mRequestName) {
+            if (mPayerName == null && contact.mPayerName != null) return false;
+            if (mPayerName != null && contact.mPayerName != null
+                    && !mPayerName.equalsIgnoreCase(contact.mPayerName)) {
+                return false;
+            }
+        }
+
+        if (mRequestPhone) {
+            if (mPayerPhone == null && contact.mPayerPhone != null) return false;
+            if (mPayerPhone != null && contact.mPayerPhone != null
+                    && !TextUtils.equals(mPayerPhone, contact.mPayerPhone)) {
+                return false;
+            }
+        }
+
+        if (mRequestEmail) {
+            if (mPayerEmail == null && contact.mPayerEmail != null) return false;
+            if (mPayerEmail != null && contact.mPayerEmail != null
+                    && !mPayerEmail.equalsIgnoreCase(contact.mPayerEmail)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return Returns the relevance score of this contact, based on the validity of the information
+     * requested by the merchant.
+     */
+    public int getRelevanceScore() {
+        int score = 0;
+
+        if (mRequestName && (mCompletionStatus & ContactEditor.INVALID_NAME) == 0) ++score;
+        if (mRequestPhone && (mCompletionStatus & ContactEditor.INVALID_PHONE_NUMBER) == 0) ++score;
+        if (mRequestEmail && (mCompletionStatus & ContactEditor.INVALID_EMAIL) == 0) ++score;
+
+        return score;
+    }
+
     private void setContactInfo(String guid, @Nullable String name,
             @Nullable String phone, @Nullable String email) {
         mPayerName = TextUtils.isEmpty(name) ? null : name;
@@ -104,6 +170,7 @@
     }
 
     private void updateCompletionStatus(int completionStatus) {
+        mCompletionStatus = completionStatus;
         mIsComplete = completionStatus == ContactEditor.COMPLETE;
 
         switch (completionStatus) {
@@ -123,12 +190,11 @@
                 mEditMessage = mContext.getString(R.string.payments_phone_number_required);
                 mEditTitle = mContext.getString(R.string.payments_add_phone_number);
                 break;
-            case ContactEditor.INVALID_MULTIPLE_FIELDS:
+            default:
+                // Multiple invalid fields.
                 mEditMessage = mContext.getString(R.string.payments_more_information_required);
                 mEditTitle = mContext.getString(R.string.payments_add_more_information);
                 break;
-            default:
-                assert false : "Invalid completion status code";
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java
index 8f9d16e..7950bf6f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.payments;
 
-import android.support.annotation.IntDef;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Patterns;
@@ -17,8 +16,6 @@
 import org.chromium.chrome.browser.payments.ui.EditorFieldModel.EditorFieldValidator;
 import org.chromium.chrome.browser.payments.ui.EditorModel;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -28,19 +25,15 @@
  * Contact information editor.
  */
 public class ContactEditor extends EditorBase<AutofillContact> {
-    @IntDef({INVALID_NAME, INVALID_EMAIL, INVALID_PHONE_NUMBER, INVALID_MULTIPLE_FIELDS})
-    @Retention(RetentionPolicy.SOURCE)
     public @interface CompletionStatus {}
     /** Can be sent to the merchant as-is without editing first. */
     public static final int COMPLETE = 0;
     /** The contact name is missing. */
-    public static final int INVALID_NAME = 1;
+    public static final int INVALID_NAME = 1 << 0;
     /** The contact email is invalid or missing. */
-    public static final int INVALID_EMAIL = 2;
+    public static final int INVALID_EMAIL = 1 << 1;
     /** The contact phone number is invalid or missing. */
-    public static final int INVALID_PHONE_NUMBER = 3;
-    /** Multiple fields are invalid or missing. */
-    public static final int INVALID_MULTIPLE_FIELDS = 4;
+    public static final int INVALID_PHONE_NUMBER = 1 << 2;
 
     private final boolean mRequestPayerName;
     private final boolean mRequestPayerPhone;
@@ -80,26 +73,18 @@
     @CompletionStatus
     public int checkContactCompletionStatus(
             @Nullable String name, @Nullable String phone, @Nullable String email) {
-        int invalidFieldCount = 0;
         int completionStatus = COMPLETE;
 
         if (mRequestPayerName && TextUtils.isEmpty(name)) {
-            invalidFieldCount++;
-            completionStatus = INVALID_NAME;
+            completionStatus |= INVALID_NAME;
         }
 
         if (mRequestPayerPhone && !getPhoneValidator().isValid(phone)) {
-            invalidFieldCount++;
-            completionStatus = INVALID_PHONE_NUMBER;
+            completionStatus |= INVALID_PHONE_NUMBER;
         }
 
         if (mRequestPayerEmail && !getEmailValidator().isValid(email)) {
-            invalidFieldCount++;
-            completionStatus = INVALID_EMAIL;
-        }
-
-        if (invalidFieldCount > 1) {
-            completionStatus = INVALID_MULTIPLE_FIELDS;
+            completionStatus |= INVALID_EMAIL;
         }
 
         return completionStatus;
@@ -139,7 +124,8 @@
 
         final AutofillContact contact = toEdit == null
                 ? new AutofillContact(mContext, new AutofillProfile(), null, null, null,
-                          INVALID_MULTIPLE_FIELDS)
+                          INVALID_NAME | INVALID_PHONE_NUMBER | INVALID_EMAIL, mRequestPayerName,
+                          mRequestPayerPhone, mRequestPayerEmail)
                 : toEdit;
 
         final EditorFieldModel nameField = mRequestPayerName
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 8f050bfe..f7c6cbcc4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -363,11 +363,12 @@
         }
 
         if (requestShipping) {
-            createShippingSection(profiles);
+            createShippingSection(Collections.unmodifiableList(profiles));
         }
 
         if (requestPayerName || requestPayerPhone || requestPayerEmail) {
-            createContactSection(profiles, requestPayerName, requestPayerPhone, requestPayerEmail);
+            createContactSection(Collections.unmodifiableList(profiles), requestPayerName,
+                    requestPayerPhone, requestPayerEmail);
         }
 
         mUI = new PaymentRequestUI(mContext, this, requestShipping,
@@ -391,11 +392,11 @@
                 requestPayerPhone, requestShipping, requestPayerName);
     }
 
-    private void createShippingSection(List<AutofillProfile> profiles) {
+    private void createShippingSection(List<AutofillProfile> unmodifiableProfiles) {
         List<AutofillAddress> addresses = new ArrayList<>();
 
-        for (int i = 0; i < profiles.size(); i++) {
-            AutofillProfile profile = profiles.get(i);
+        for (int i = 0; i < unmodifiableProfiles.size(); i++) {
+            AutofillProfile profile = unmodifiableProfiles.get(i);
             mAddressEditor.addPhoneNumberIfValid(profile.getPhoneNumber());
 
             // Only suggest addresses that have a street address.
@@ -440,14 +441,16 @@
                 PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, firstCompleteAddressIndex, addresses);
     }
 
-    private void createContactSection(List<AutofillProfile> profiles, boolean requestName,
-            boolean requestPhone, boolean requestEmail) {
-        Set<String> uniqueContactInfos = new HashSet<>();
-        mContactEditor = new ContactEditor(requestName, requestPhone, requestEmail);
+    private void createContactSection(List<AutofillProfile> unmodifiableProfiles,
+            boolean requestName, boolean requestPhone, boolean requestEmail) {
         List<AutofillContact> contacts = new ArrayList<>();
+        List<AutofillContact> uniqueContacts = new ArrayList<>();
+        mContactEditor = new ContactEditor(requestName, requestPhone, requestEmail);
 
-        for (int i = 0; i < profiles.size(); i++) {
-            AutofillProfile profile = profiles.get(i);
+        // Add the profile's valid request values to the editor's autocomplete list and convert
+        // relevant profiles to AutofillContacts.
+        for (int i = 0; i < unmodifiableProfiles.size(); ++i) {
+            AutofillProfile profile = unmodifiableProfiles.get(i);
             String name = requestName && !TextUtils.isEmpty(profile.getFullName())
                     ? profile.getFullName()
                     : null;
@@ -457,44 +460,62 @@
             String email = requestEmail && !TextUtils.isEmpty(profile.getEmailAddress())
                     ? profile.getEmailAddress()
                     : null;
+
+            // Add the values to the editor's autocomplete list.
             mContactEditor.addPayerNameIfValid(name);
             mContactEditor.addPhoneNumberIfValid(phone);
             mContactEditor.addEmailAddressIfValid(email);
 
+            // Only create a contact if the profile has relevant information for the merchant.
             if (name != null || phone != null || email != null) {
-                // Different profiles can have identical contact info. Do not add the same
-                // contact info to the list twice.
-                String uniqueContactInfo = name + phone + email;
-                if (!uniqueContactInfos.contains(uniqueContactInfo)) {
-                    uniqueContactInfos.add(uniqueContactInfo);
-
-                    @ContactEditor.CompletionStatus
-                    int completionStatus =
-                            mContactEditor.checkContactCompletionStatus(name, phone, email);
-                    contacts.add(new AutofillContact(
-                            mContext, profile, name, phone, email, completionStatus));
-                }
+                contacts.add(new AutofillContact(mContext, profile, name, phone, email,
+                        mContactEditor.checkContactCompletionStatus(name, phone, email),
+                        requestName, requestPhone, requestEmail));
             }
         }
 
-        // Suggest complete contact infos first.
-        Collections.sort(contacts, COMPLETENESS_COMPARATOR);
+        // Order the contacts so the ones that have most of the required information are put first.
+        // The sort is stable, so contacts with the same relevance score are sorted by frecency.
+        Collections.sort(contacts, new Comparator<AutofillContact>() {
+            @Override
+            public int compare(AutofillContact a, AutofillContact b) {
+                return b.getRelevanceScore() - a.getRelevanceScore();
+            }
+        });
 
-        // Limit the number of suggestions.
-        contacts = contacts.subList(0, Math.min(contacts.size(), SUGGESTIONS_LIMIT));
+        // This algorithm is quadratic, but since the number of contacts is generally very small
+        // ( < 10) a faster but more complicated algorithm would be overkill.
+        for (int i = 0; i < contacts.size(); i++) {
+            AutofillContact contact = contacts.get(i);
+
+            // Different contacts can have identical info. Do not add the same contact info or a
+            // subset of it twice. It's important that the profiles be sorted by the quantity of
+            // required info they have.
+            boolean isNewSuggestion = true;
+            for (int j = 0; j < uniqueContacts.size(); ++j) {
+                if (uniqueContacts.get(j).isEqualOrSupersetOf(contact)) {
+                    isNewSuggestion = false;
+                    break;
+                }
+            }
+            if (isNewSuggestion) uniqueContacts.add(contact);
+
+            // Limit the number of suggestions.
+            if (uniqueContacts.size() == SUGGESTIONS_LIMIT) break;
+        }
 
         // Log the number of suggested contact infos.
         mJourneyLogger.setNumberOfSuggestionsShown(
-                PaymentRequestJourneyLogger.SECTION_CONTACT_INFO, contacts.size());
+                PaymentRequestJourneyLogger.SECTION_CONTACT_INFO, uniqueContacts.size());
 
         // Automatically select the first address if it is complete.
         int firstCompleteContactIndex = SectionInformation.NO_SELECTION;
-        if (!contacts.isEmpty() && contacts.get(0).isComplete()) {
+        if (!uniqueContacts.isEmpty() && uniqueContacts.get(0).isComplete()) {
             firstCompleteContactIndex = 0;
         }
 
         mContactSection = new SectionInformation(
-                PaymentRequestUI.TYPE_CONTACT_DETAILS, firstCompleteContactIndex, contacts);
+                PaymentRequestUI.TYPE_CONTACT_DETAILS, firstCompleteContactIndex, uniqueContacts);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
index 95289b7..6309c07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
@@ -1079,14 +1079,13 @@
         view.setMovementMethod(LinkMovementMethod.getInstance());
         ApiCompatibilityUtils.setTextAppearance(view, R.style.PaymentsUiSectionDescriptiveText);
 
-        LinearLayout.LayoutParams layoutParams =
-                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
-        int marginSize = mContext.getResources().getDimensionPixelSize(
+        // Add paddings instead of margin to let getMeasuredHeight return correct value for section
+        // resize animation.
+        int paddingSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.payments_section_large_spacing);
-        layoutParams.topMargin = marginSize;
-        ApiCompatibilityUtils.setMarginStart(layoutParams, marginSize);
-        ApiCompatibilityUtils.setMarginEnd(layoutParams, marginSize);
-        parent.addView(view, layoutParams);
+        ApiCompatibilityUtils.setPaddingRelative(
+                view, paddingSize, paddingSize, paddingSize, paddingSize);
+        parent.addView(view);
     }
 
     private Callback<SectionInformation> createUpdateSectionCallback(@DataType final int type) {
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 42de120..6ddd444 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1675,7 +1675,10 @@
       <message name="IDS_DOWNLOAD_STARTED" desc="Message to show when download has started [CHAR LIMIT=30]">
         Downloading…
       </message>
-      <message name="IDS_DOWNLOAD_SUCCEEDED_MESSAGE" desc="Transient message shown when a file download has succeeded." meaning="Android">
+      <message name="IDS_DOWNLOAD_SUCCEEDED_MESSAGE" desc="App-based transient message shown when a file download has succeeded." meaning="Android">
+        <ph name="FILE_NAME">%1$s<ex>http://abc.com/test.pdf</ex></ph> downloaded in <ph name="PRODUCT_NAME">%2$s<ex>Chrome</ex></ph>
+      </message>
+      <message name="IDS_DOWNLOAD_SUCCEEDED_MESSAGE_DEFAULT" desc="Transient message shown when a file download has succeeded." meaning="Android">
         <ph name="FILE_NAME">%1$s<ex>http://abc.com/test.pdf</ex></ph> downloaded
       </message>
       <message name="IDS_REMAINING_DURATION_DAYS" desc="Message to show remaining duration in multiple days">
@@ -2083,7 +2086,10 @@
       <message name="IDS_BOOKMARKS_FOLDER_EMPTY" desc="Text explaining that the currently selected bookmarks folder is empty.">
         No bookmarks here
       </message>
-      <message name="IDS_BOOKMARK_PAGE_SAVED" desc="Message shown after user adds a new bookmark. [CHAR-LIMIT=32]">
+      <message name="IDS_BOOKMARK_PAGE_SAVED" desc="App-based message shown after user adds a new bookmark. [CHAR-LIMIT=32]">
+        Bookmarked in <ph name="PRODUCT_NAME">%1$s<ex>Chrome</ex></ph>
+      </message>
+      <message name="IDS_BOOKMARK_PAGE_SAVED_DEFAULT" desc="Default message shown after user adds a new bookmark. [CHAR-LIMIT=32]">
         Bookmarked
       </message>
       <message name="IDS_BOOKMARK_PAGE_SAVED_FOLDER" desc="Message shown after user adds a new bookmark. Also specifies in which folder the bookmark was added. [CHAR-LIMIT=32]">
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index aeebea4..f26e4e7 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1353,6 +1353,7 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java",
+  "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java",
@@ -1531,6 +1532,7 @@
   "junit/src/org/chromium/chrome/browser/omaha/ResponseParserTest.java",
   "junit/src/org/chromium/chrome/browser/omaha/VersionNumberTest.java",
   "junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java",
+  "junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java",
   "junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java",
   "junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
   "junit/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProviderUnitTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 8916788..13020919 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -97,7 +97,7 @@
  */
 public class CustomTabActivityTest extends CustomTabActivityTestBase {
     private static final int MAX_MENU_CUSTOM_ITEMS = 5;
-    private static final int NUM_CHROME_MENU_ITEMS = 2;
+    private static final int NUM_CHROME_MENU_ITEMS = 5;
     private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
     private static final String TEST_PAGE_2 = "/chrome/test/data/android/test.html";
     private static final String GEOLOCATION_PAGE =
@@ -375,13 +375,16 @@
         assertNotNull("App menu is not initialized: ", menu);
         assertEquals(expectedMenuSize, actualMenuSize);
         assertNotNull(menu.findItem(R.id.forward_menu_id));
+        assertNotNull(menu.findItem(R.id.bookmark_this_page_id));
+        assertNotNull(menu.findItem(R.id.offline_page_id));
         assertNotNull(menu.findItem(R.id.info_menu_id));
         assertNotNull(menu.findItem(R.id.reload_menu_id));
         assertNotNull(menu.findItem(R.id.open_in_browser_id));
         assertFalse(menu.findItem(R.id.share_row_menu_id).isVisible());
         assertFalse(menu.findItem(R.id.share_row_menu_id).isEnabled());
-        assertNull(menu.findItem(R.id.bookmark_this_page_id));
-        assertNull(menu.findItem(R.id.find_in_page_id));
+        assertNotNull(menu.findItem(R.id.find_in_page_id));
+        assertNotNull(menu.findItem(R.id.add_to_homescreen_id));
+        assertNotNull(menu.findItem(R.id.request_desktop_site_id));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
index 40df7425..7dab341 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestContactDetailsTest.java
@@ -34,6 +34,26 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
+        // Add the same profile but with a different address.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
+                "999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without a phone number.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without an email.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "" /* emailAddress */, "en-US"));
+
+        // Add the same profile but without a name.
+        helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
+                "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
         installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
@@ -208,6 +228,19 @@
     }
 
     /**
+     * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
+     * to the user.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testSuggestionsDeduped()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+    }
+
+    /**
      * Test that starting a payment request that requires the user's email address, phone number and
      * name results in the appropriate metric being logged in the
      * PaymentRequest.RequestedInformation histogram.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java
new file mode 100644
index 0000000..13abdbf
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java
@@ -0,0 +1,132 @@
+// 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.
+
+package org.chromium.chrome.browser.payments;
+
+import android.support.test.filters.MediumTest;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.autofill.AutofillTestHelper;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A payment integration test for a merchant that requests email address and a phone number.
+ */
+public class PaymentRequestEmailAndPhoneTest extends PaymentRequestTestBase {
+    public PaymentRequestEmailAndPhoneTest() {
+        // This merchant request an email address and a phone number.
+        super("payment_request_email_and_phone_test.html");
+    }
+
+    @Override
+    public void onMainActivityStarted()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        AutofillTestHelper helper = new AutofillTestHelper();
+        // The user has a valid email address and phone number on disk.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but with a different address.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
+                "999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without a phone number.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phoneNumber */,
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without an email.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "" /* emailAddress */, "en-US"));
+
+        // Add the same profile but without a name.
+        helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
+                "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
+    }
+
+    /** Provide the existing valid email address and phone number to the merchant. */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickAndWait(R.id.button_primary, mDismissed);
+        expectResultContains(new String[] {"555-555-5555", "jon.doe@google.com"});
+    }
+
+    /** Attempt to add an invalid email address and phone number and cancel the transaction. */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testAddInvalidEmailAndCancel()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        clickInContactInfoAndWait(R.id.payments_add_option_button, mReadyToEdit);
+        setTextInEditorAndWait(new String[] {"-1-", "jane.jones"}, mEditorTextUpdate);
+        clickInEditorAndWait(R.id.payments_edit_done_button, mEditorValidationError);
+        clickInEditorAndWait(R.id.payments_edit_cancel_button, mReadyToPay);
+        clickAndWait(R.id.close_button, mDismissed);
+        expectResultContains(new String[] {"Request cancelled"});
+    }
+
+    /** Add a new email address and phone number and provide that to the merchant. */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testAddEmailAndPhoneAndPay()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        clickInContactInfoAndWait(R.id.payments_add_option_button, mReadyToEdit);
+        setTextInEditorAndWait(
+                new String[] {"555-555-5555", "jane.jones@google.com"}, mEditorTextUpdate);
+        clickInEditorAndWait(R.id.payments_edit_done_button, mReadyToPay);
+        clickAndWait(R.id.button_primary, mDismissed);
+        expectResultContains(new String[] {"555-555-5555", "jane.jones@google.com"});
+    }
+
+    /**
+     * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
+     * to the user.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testSuggestionsDeduped()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+    }
+
+    /**
+     * Test that starting a payment request that requires only the user's email address results in
+     * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testRequestedInformationMetric()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        // Start the Payment Request.
+        triggerUIAndWait(mReadyToPay);
+
+        int appropriateEnumValue = PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
+                | PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE;
+
+        // Make sure that only the appropriate enum value was logged.
+        for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
+            assertEquals((i == (appropriateEnumValue) ? 1 : 0),
+                    RecordHistogram.getHistogramValueCountForTesting(
+                            "PaymentRequest.RequestedInformation", i));
+        }
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
index 103feb371..282dcc8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java
@@ -33,6 +33,26 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
+        // Add the same profile but with a different address.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
+                "999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without a phone number.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without an email.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "" /* emailAddress */, "en-US"));
+
+        // Add the same profile but without a name.
+        helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
+                "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
         installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
@@ -76,6 +96,19 @@
     }
 
     /**
+     * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
+     * to the user.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testSuggestionsDeduped()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+    }
+
+    /**
      * Test that starting a payment request that requires only the user's email address results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
index d6c235b0..ad53450 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestMultipleContactDetailsTest.java
@@ -21,36 +21,69 @@
  */
 public class PaymentRequestMultipleContactDetailsTest extends PaymentRequestTestBase {
     private static final AutofillProfile[] AUTOFILL_PROFILES = {
-            // Incomplete (no phone) profile.
+            // 0 - Incomplete (no phone) profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
                     "Bart Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
                     "90210", "", "US", "", "bart@simpson.com", ""),
 
-            // Incomplete (no email) profile.
+            // 1 - Incomplete (no email) profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
                     "Homer Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
                     "90210", "", "US", "555 123-4567", "", ""),
 
-            // Complete profile.
+            // 2 - Complete profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
                     "Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
                     "90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
 
-            // Complete profile.
+            // 3 - Complete profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
                     "Maggie Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
                     "90210", "", "US", "555 123-4567", "maggie@simpson.com", ""),
 
-            // Incomplete (no phone and email) profile.
+            // 4 - Incomplete (no phone and email) profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
                     "Marge Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
                     "90210", "", "US", "", "", ""),
 
-            // Incomplete (no name) profile.
+            // 5 - Incomplete (no name) profile.
             new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */, "",
                     "Acme Inc.", "123 Main", "California", "Los Angeles", "", "90210", "", "US",
                     "555 123-4567", "marge@simpson.com", ""),
 
+            // These profiles are used to test the dedupe of subset suggestions. They are based on
+            // The Lisa Simpson profile.
+
+            // 6 - Same as original, but with no name.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "" /* name */, "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
+
+            // 7 - Same as original, but with no phone.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "" /* phoneNumber */, "lisa@simpson.com", ""),
+
+            // 8 - Same as original, but with no email.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "555 123-4567", "" /* emailAddress */, ""),
+
+            // 9 - Same as original, but with no phone and no email.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "" /* phoneNumber */, "" /* emailAddress */, ""),
+
+            // 10 - Has an email address that is a superset of the original profile's email.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "555 123-4567", "fakelisa@simpson.com", ""),
+
+            // 11 - Has the same name as the original but with no capitalization in the name.
+            new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
+                    "lisa simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
+                    "90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
+
     };
 
     private AutofillProfile[] mProfilesToAdd;
@@ -131,9 +164,80 @@
                 getContactDetailsSuggestionLabel(0));
         assertEquals(
                 "Homer Simpson\n555 123-4567\nEmail required", getContactDetailsSuggestionLabel(1));
-        assertEquals(
-                "Marge Simpson\nMore information required", getContactDetailsSuggestionLabel(2));
         assertEquals("555 123-4567\nmarge@simpson.com\nName required",
-                getContactDetailsSuggestionLabel(3));
+                getContactDetailsSuggestionLabel(2));
+        assertEquals(
+                "Marge Simpson\nMore information required", getContactDetailsSuggestionLabel(3));
+    }
+
+    /**
+     * Makes sure that suggestions that are subsets of other fields (empty values) are deduped.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testContactDetailsDedupe_EmptyFields()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        // Add the original profile and a bunch of similar profiles with missing fields.
+        // Make sure the original profile is suggested last, to test that the suggestions are
+        // sorted by completeness.
+        mProfilesToAdd = new AutofillProfile[] {
+                AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[6], AUTOFILL_PROFILES[7],
+                AUTOFILL_PROFILES[8], AUTOFILL_PROFILES[9],
+        };
+        mCountsToSet = new int[] {1, 20, 15, 10, 5};
+        mDatesToSet = new int[] {1000, 4000, 3000, 2000, 1000};
+
+        triggerUIAndWait(mReadyForInput);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+
+        // Only the original profile with all the fields should be suggested.
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                getContactDetailsSuggestionLabel(0));
+    }
+
+    /**
+     * Makes sure that suggestions where some fields values are equal but with different case are
+     * deduped.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testContactDetailsDedupe_Capitalization()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        // Add the original profile and the one where the the name is not capitalized.
+        // Make sure the original profile is suggested first (no particular reason).
+        mProfilesToAdd = new AutofillProfile[] {AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[11]};
+        mCountsToSet = new int[] {15, 5};
+        mDatesToSet = new int[] {5000, 2000};
+
+        triggerUIAndWait(mReadyForInput);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                getContactDetailsSuggestionLabel(0));
+    }
+
+    /**
+     * Makes sure that suggestions where some fields values are subsets of the other are not
+     * deduped.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testContactDetailsDontDedupe_FieldSubset()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        // Add the original profile and the one where the email is a superset of the original.
+        // Make sure the one with the superset is suggested first, because to test the subset one
+        // needs to be added after.
+        mProfilesToAdd = new AutofillProfile[] {AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[10]};
+        mCountsToSet = new int[] {15, 25};
+        mDatesToSet = new int[] {5000, 7000};
+
+        triggerUIAndWait(mReadyForInput);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(2, getNumberOfContactDetailSuggestions());
+        assertEquals("Lisa Simpson\n555 123-4567\nfakelisa@simpson.com",
+                getContactDetailsSuggestionLabel(0));
+        assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
+                getContactDetailsSuggestionLabel(1));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
index 594ba10..cc92694 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestNameTest.java
@@ -37,6 +37,26 @@
         helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
                 "4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
                 billingAddressId, "" /* serverId */));
+
+        // Add the same profile but with a different address.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
+                "999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without a phone number.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without an email.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "" /* emailAddress */, "en-US"));
+
+        // Add the same profile but without a name.
+        helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
+                "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
     }
 
     /** Provide the existing valid payer name to the merchant. */
@@ -82,6 +102,19 @@
     }
 
     /**
+     * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
+     * to the user.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testSuggestionsDeduped()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+    }
+
+    /**
      * Test that starting a payment request that requires only the user's payer name results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
index 6d1c9e4..94a8650 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java
@@ -33,6 +33,26 @@
                 "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
                 "jon.doe@google.com", "en-US"));
 
+        // Add the same profile but with a different address.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
+                "999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without a phone number.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
+                "jon.doe@google.com", "en-US"));
+
+        // Add the same profile but without an email.
+        helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
+                "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "" /* emailAddress */, "en-US"));
+
+        // Add the same profile but without a name.
+        helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
+                "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
+                "jon.doe@google.com", "en-US"));
+
         installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
     }
 
@@ -76,6 +96,19 @@
     }
 
     /**
+     * Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
+     * to the user.
+     */
+    @MediumTest
+    @Feature({"Payments"})
+    public void testSuggestionsDeduped()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        triggerUIAndWait(mReadyToPay);
+        clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
+        assertEquals(1, getNumberOfContactDetailSuggestions());
+    }
+
+    /**
      * Test that starting a payment request that requires only the user's phone number results in
      * the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
      */
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java
index 55039d5c..4060b95 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java
@@ -24,7 +24,7 @@
 import java.util.Collection;
 
 /**
- * Unit tests for the AutofillContact class.
+ * Parametrized unit tests for the AutofillContact class.
  */
 @RunWith(ParameterizedRobolectricTestRunner.class)
 @Config(sdk = 21, manifest = Config.NONE)
@@ -107,8 +107,9 @@
     public void test() {
         AutofillProfile profile = new AutofillProfile();
         AutofillContact contact = new AutofillContact(mContext, profile, mPayerName, mPayerPhone,
-                mPayerEmail,
-                mIsComplete ? ContactEditor.COMPLETE : ContactEditor.INVALID_MULTIPLE_FIELDS);
+                mPayerEmail, mIsComplete ? ContactEditor.COMPLETE
+                                         : ContactEditor.INVALID_NAME | ContactEditor.INVALID_EMAIL,
+                true, true, true);
 
         Assert.assertEquals(
                 mIsComplete ? "Contact should be complete" : "Contact should be incomplete",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java
new file mode 100644
index 0000000..4ba84d81
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java
@@ -0,0 +1,194 @@
+// 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.
+
+package org.chromium.chrome.browser.payments;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+
+/**
+ * Unit tests for the AutofillContact class.
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class AutofillContactUnitTest {
+    private static final String MESSAGE = "message";
+    private static final String NAME = "Jon Doe";
+    private static final String PHONE = "555-555-555";
+    private static final String EMAIL = "jon@doe.com";
+
+    @Test
+    public void testIsEqualOrSupersetOf_RequestAllFields() {
+        AutofillProfile dummyProfile = new AutofillProfile();
+        Context mockContext = spy(RuntimeEnvironment.application);
+        doReturn(MESSAGE).when(mockContext).getString(anyInt());
+
+        AutofillContact contact1 = new AutofillContact(mockContext, dummyProfile, NAME, PHONE,
+                EMAIL, ContactEditor.COMPLETE, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        AutofillContact contact2 = new AutofillContact(mockContext, dummyProfile, NAME, PHONE,
+                EMAIL, ContactEditor.COMPLETE, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+
+        // The return value should be true for identical profiles.
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+
+        // The return value should be true if the second profile is missing fields.
+        contact2.completeContact("", "", PHONE, EMAIL);
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", NAME, "", EMAIL);
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", NAME, PHONE, "");
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", NAME, "", "");
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", "", PHONE, "");
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", "", "", EMAIL);
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", "", "", "");
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+
+        // The return value should be false if one field is different.
+        contact2.completeContact("", "diff", PHONE, EMAIL);
+        Assert.assertFalse(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", NAME, "diff", EMAIL);
+        Assert.assertFalse(contact1.isEqualOrSupersetOf(contact2));
+        contact2.completeContact("", NAME, PHONE, "diff");
+        Assert.assertFalse(contact1.isEqualOrSupersetOf(contact2));
+    }
+
+    @Test
+    public void testIsEqualOrSupersetOf_RequestSomeFields() {
+        AutofillProfile dummyProfile = new AutofillProfile();
+        Context mockContext = spy(RuntimeEnvironment.application);
+        doReturn(MESSAGE).when(mockContext).getString(anyInt());
+
+        // The merchant does not request a name.
+        AutofillContact contact1 = new AutofillContact(mockContext, dummyProfile, NAME, PHONE,
+                EMAIL, ContactEditor.COMPLETE, false /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        AutofillContact contact2 = new AutofillContact(mockContext, dummyProfile, NAME, PHONE,
+                EMAIL, ContactEditor.COMPLETE, false /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+
+        // The return value should be true for identical profiles.
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+
+        // The return value should be true even if the name is missing.
+        contact2.completeContact("", "", PHONE, EMAIL);
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+
+        // The return value should be true even if the name is different.
+        contact2.completeContact("", "diff", PHONE, EMAIL);
+        Assert.assertTrue(contact1.isEqualOrSupersetOf(contact2));
+    }
+
+    @Test
+    public void testGetRelevanceScore_RequestAllFields() {
+        AutofillProfile dummyProfile = new AutofillProfile();
+        Context mockContext = spy(RuntimeEnvironment.application);
+        doReturn(MESSAGE).when(mockContext).getString(anyInt());
+
+        // The merchant requests all fields.
+        // Since all requested fields are present and valid, The score should be 3.
+        AutofillContact contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.COMPLETE, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(3, contact.getRelevanceScore());
+
+        // The name is not valid, the score should be 2.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_NAME, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(2, contact.getRelevanceScore());
+
+        // The phone is not valid, the score should be 2.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_PHONE_NUMBER, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(2, contact.getRelevanceScore());
+
+        // The email is not valid, the score should be 2.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_EMAIL, true /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(2, contact.getRelevanceScore());
+
+        // The name and phone are not valid, the score should be 1.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_NAME | ContactEditor.INVALID_PHONE_NUMBER,
+                true /* requestName */, true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(1, contact.getRelevanceScore());
+
+        // The name and email are not valid, the score should be 1.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_NAME | ContactEditor.INVALID_EMAIL, true /* requestName */,
+                true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(1, contact.getRelevanceScore());
+
+        // The phone and email are not valid, the score should be 1.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_PHONE_NUMBER | ContactEditor.INVALID_EMAIL,
+                true /* requestName */, true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(1, contact.getRelevanceScore());
+
+        // The name, phone and email are not valid, the score should be 0.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_NAME | ContactEditor.INVALID_PHONE_NUMBER
+                        | ContactEditor.INVALID_EMAIL,
+                true /* requestName */, true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(0, contact.getRelevanceScore());
+    }
+
+    @Test
+    public void testGetRelevanceScore_RequestSomeFields() {
+        AutofillProfile dummyProfile = new AutofillProfile();
+        Context mockContext = spy(RuntimeEnvironment.application);
+        doReturn(MESSAGE).when(mockContext).getString(anyInt());
+
+        // The merchant does not request a name.
+        // Since all requested fields are present and valid, The score should be 2.
+        AutofillContact contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.COMPLETE, false /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(2, contact.getRelevanceScore());
+
+        // The name is not valid, the score should still be 2.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_NAME, false /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(2, contact.getRelevanceScore());
+
+        // The phone is not valid, the score should be 1.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_PHONE_NUMBER, false /* requestName */,
+                true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(1, contact.getRelevanceScore());
+
+        // The email is not valid, the score should be 1.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_EMAIL, false /* requestName */, true /* requestPhone */,
+                true /* requestEmail */);
+        Assert.assertEquals(1, contact.getRelevanceScore());
+
+        // The phone and email are not valid, the score should be 0.
+        contact = new AutofillContact(mockContext, dummyProfile, NAME, PHONE, EMAIL,
+                ContactEditor.INVALID_PHONE_NUMBER | ContactEditor.INVALID_EMAIL,
+                false /* requestName */, true /* requestPhone */, true /* requestEmail */);
+        Assert.assertEquals(0, contact.getRelevanceScore());
+    }
+}
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 18d26a1..71f3705 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -14132,6 +14132,32 @@
         This tab is connected to a USB device.
       </message>
 
+      <!-- Tab accessibility labels -->
+      <message name="IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT" desc="Accessibility label text, when the tab is recording media. Example: 'Google Hangouts - Camera or microphone recording'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - Camera or microphone recording
+      </message>
+      <message name="IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT" desc="Accessibility label text, when the tab content is being captured and shared using screen sharing. Example: 'Google Hangouts - Tab content shared'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - Tab content shared
+      </message>
+      <message name="IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT" desc="Accessibility label text, when the tab is playing audio. Example: 'Google Play Music - Audio playing'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Play Music</ex></ph> - Audio playing
+      </message>
+      <message name="IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT" desc="Accessibility label text, when all tab audio output is muted. Example: 'Google Play Music - Audio muted'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Play Music</ex></ph> - Audio muted
+      </message>
+      <message name="IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT" desc="Accessibility label text, when a tab is connected to and has access to a Bluetooth device. Example: 'Google Photos - Bluetooth device connected'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Photos</ex></ph> - Bluetooth device connected
+      </message>
+      <message name="IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT" desc="Accessibility label text, when a tab is connected to a USB device. Example: 'Google Photos - USB device connected'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Photos</ex></ph> - USB device connected
+      </message>
+      <message name="IDS_TAB_AX_LABEL_NETWORK_ERROR_FORMAT" desc="Accessibility label text, when tab has encountered a network error such as being disconnected from the internet. Example: 'www.google.com - Network error'">
+        <ph name="WINDOW_TITLE">$1<ex>www.google.com</ex></ph> - Network error
+      </message>
+      <message name="IDS_TAB_AX_LABEL_CRASHED_FORMAT" desc="Accessibility label text, when a tab has crashed. Example: 'Google Search - Crashed'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Search</ex></ph> - Crashed
+      </message>
+
       <!-- ProcessSingleton -->
       <message name="IDS_PROFILE_IN_USE_LINUX_QUIT" desc="Text of button in profile in use dialog to quit without doing anything.">
         Quit
diff --git a/chrome/browser/chromeos/extensions/wallpaper_manager_util.cc b/chrome/browser/chromeos/extensions/wallpaper_manager_util.cc
index d21ef00b..13729cf 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_manager_util.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_manager_util.cc
@@ -28,10 +28,6 @@
 
 namespace {
 
-const char kAndroidWallpapersAppPackage[] = "com.google.android.apps.wallpaper";
-const char kAndroidWallpapersAppActivity[] =
-    "com.google.android.apps.wallpaper.picker.CategoryPickerActivity";
-
 const char kAndroidWallpapersAppTrialName[] = "AndroidWallpapersAppOnChromeOS";
 const char kEnableAndroidWallpapersApp[] =
     "Enable-android-wallpapers-app_Dogfood";
@@ -54,7 +50,8 @@
 
   // Check if Android Wallpapers App has been installed.
   const ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile);
-  if (!prefs || prefs->GetAppsForPackage(kAndroidWallpapersAppPackage).empty())
+  if (!prefs ||
+      prefs->GetAppsForPackage(arc::kAndroidWallpapersAppPackage).empty())
     return false;
 
   // Check if the finch experiment or the chrome flag is enabled.
@@ -74,7 +71,7 @@
 
   if (ShouldUseAndroidWallpapersApp(profile)) {
     const std::string app_id = ArcAppListPrefs::GetAppId(
-        kAndroidWallpapersAppPackage, kAndroidWallpapersAppActivity);
+        arc::kAndroidWallpapersAppPackage, arc::kAndroidWallpapersAppActivity);
     arc::LaunchApp(profile, app_id, ui::EF_NONE);
   } else {
     ExtensionService* service =
diff --git a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
index 0b031e8..7aa154e2 100644
--- a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
+++ b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.cc
@@ -47,7 +47,8 @@
 AutoEnrollmentCheckScreen::AutoEnrollmentCheckScreen(
     BaseScreenDelegate* base_screen_delegate,
     AutoEnrollmentCheckScreenActor* actor)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kAutoEnrollmentCheckScreenName),
       actor_(actor),
       auto_enrollment_controller_(nullptr),
       captive_portal_status_(
@@ -120,10 +121,6 @@
 void AutoEnrollmentCheckScreen::Hide() {
 }
 
-std::string AutoEnrollmentCheckScreen::GetName() const {
-  return WizardController::kAutoEnrollmentCheckScreenName;
-}
-
 void AutoEnrollmentCheckScreen::OnActorDestroyed(
     AutoEnrollmentCheckScreenActor* actor) {
   if (actor_ == actor)
diff --git a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
index ce4241da..b9ac5f1 100644
--- a/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
+++ b/chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen.h
@@ -50,7 +50,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // AutoEnrollmentCheckScreenActor::Delegate implementation:
   void OnActorDestroyed(AutoEnrollmentCheckScreenActor* actor) override;
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_screen.cc b/chrome/browser/chromeos/login/enrollment/enrollment_screen.cc
index 9e6cee7..e582634 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_screen.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_screen.cc
@@ -64,7 +64,7 @@
 
 EnrollmentScreen::EnrollmentScreen(BaseScreenDelegate* base_screen_delegate,
                                    EnrollmentScreenActor* actor)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate, WizardController::kEnrollmentScreenName),
       actor_(actor),
       weak_ptr_factory_(this) {}
 
@@ -166,10 +166,6 @@
   weak_ptr_factory_.InvalidateWeakPtrs();
 }
 
-std::string EnrollmentScreen::GetName() const {
-  return WizardController::kEnrollmentScreenName;
-}
-
 void EnrollmentScreen::AuthenticateUsingAttestation() {
   VLOG(1) << "Authenticating using attestation.";
   elapsed_timer_.reset(new base::ElapsedTimer());
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_screen.h b/chrome/browser/chromeos/login/enrollment/enrollment_screen.h
index d7b7bd6e..6ccefec9 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_screen.h
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_screen.h
@@ -56,7 +56,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // EnrollmentScreenActor::Controller implementation:
   void OnLoginDone(const std::string& user,
diff --git a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
index 1da3072..6c6dc8ae 100644
--- a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
+++ b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
@@ -17,7 +17,9 @@
 ArcTermsOfServiceScreen::ArcTermsOfServiceScreen(
     BaseScreenDelegate* base_screen_delegate,
     ArcTermsOfServiceScreenActor* actor)
-    : BaseScreen(base_screen_delegate), actor_(actor) {
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kArcTermsOfServiceScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -41,10 +43,6 @@
     actor_->Hide();
 }
 
-std::string ArcTermsOfServiceScreen::GetName() const {
-  return WizardController::kArcTermsOfServiceScreenName;
-}
-
 void ArcTermsOfServiceScreen::OnSkip() {
   ApplyTerms(false);
 }
diff --git a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
index b046ea8..f2a9d92 100644
--- a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
+++ b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
@@ -25,7 +25,6 @@
   // BaseScreen:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // ArcTermsOfServiceScreenActor::Delegate:
   void OnSkip() override;
diff --git a/chrome/browser/chromeos/login/screens/base_screen.cc b/chrome/browser/chromeos/login/screens/base_screen.cc
index 55397f17..8df5379 100644
--- a/chrome/browser/chromeos/login/screens/base_screen.cc
+++ b/chrome/browser/chromeos/login/screens/base_screen.cc
@@ -67,9 +67,9 @@
   return *this;
 }
 
-BaseScreen::BaseScreen(BaseScreenDelegate* base_screen_delegate)
-    : channel_(nullptr), base_screen_delegate_(base_screen_delegate) {
-}
+BaseScreen::BaseScreen(BaseScreenDelegate* base_screen_delegate,
+                       const std::string& screen_id)
+    : base_screen_delegate_(base_screen_delegate), screen_id_(screen_id) {}
 
 BaseScreen::~BaseScreen() {
 }
@@ -92,17 +92,11 @@
   return true;
 }
 
-std::string BaseScreen::GetID() const {
-  // TODO (ygorshenin, crbug.com/433797): elimitate intermediate
-  // GetName() ASAP.
-  return GetName();
-}
-
 void BaseScreen::CommitContextChanges() {
   if (!context_.HasChanges())
     return;
   if (!channel_) {
-    LOG(ERROR) << "Model-view channel for " << GetID()
+    LOG(ERROR) << "Model-view channel for " << screen_id()
                << " is not ready, context changes are not sent to the view.";
     return;
   }
diff --git a/chrome/browser/chromeos/login/screens/base_screen.h b/chrome/browser/chromeos/login/screens/base_screen.h
index c5d0b95..ca76b0d 100644
--- a/chrome/browser/chromeos/login/screens/base_screen.h
+++ b/chrome/browser/chromeos/login/screens/base_screen.h
@@ -28,7 +28,8 @@
 // method called just once.
 class BaseScreen {
  public:
-  explicit BaseScreen(BaseScreenDelegate* base_screen_delegate);
+  explicit BaseScreen(BaseScreenDelegate* base_screen_delegate,
+                      const std::string& screen_id);
   virtual ~BaseScreen();
 
   // ---- Old implementation ----
@@ -39,9 +40,6 @@
   // Makes wizard screen invisible.
   virtual void Hide() = 0;
 
-  // Returns the screen name.
-  virtual std::string GetName() const = 0;
-
   // ---- New Implementation ----
 
   // Called to perform initialization of the screen. UI is guaranteed to exist
@@ -65,7 +63,7 @@
   virtual bool IsStatusAreaDisplayed();
 
   // Returns the identifier of the screen.
-  virtual std::string GetID() const;
+  const std::string& screen_id() const { return screen_id_; }
 
   // Called when user action event with |event_id|
   // happened. Notification about this event comes from the JS
@@ -149,9 +147,11 @@
   // counterpart.
   void OnContextChanged(const base::DictionaryValue& diff);
 
-  ModelViewChannel* channel_;
+  ModelViewChannel* channel_ = nullptr;
 
-  BaseScreenDelegate* base_screen_delegate_;
+  BaseScreenDelegate* base_screen_delegate_ = nullptr;
+
+  const std::string screen_id_;
 
   DISALLOW_COPY_AND_ASSIGN(BaseScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/controller_pairing_screen.cc b/chrome/browser/chromeos/login/screens/controller_pairing_screen.cc
index 51e7105..9bd70f03 100644
--- a/chrome/browser/chromeos/login/screens/controller_pairing_screen.cc
+++ b/chrome/browser/chromeos/login/screens/controller_pairing_screen.cc
@@ -20,7 +20,8 @@
     Delegate* delegate,
     ControllerPairingScreenActor* actor,
     ControllerPairingController* shark_controller)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kControllerPairingScreenName),
       delegate_(delegate),
       actor_(actor),
       shark_controller_(shark_controller),
@@ -64,10 +65,6 @@
     actor_->Hide();
 }
 
-std::string ControllerPairingScreen::GetName() const {
-  return WizardController::kControllerPairingScreenName;
-}
-
 void ControllerPairingScreen::PairingStageChanged(Stage new_stage) {
   DCHECK(new_stage != current_stage_);
 
diff --git a/chrome/browser/chromeos/login/screens/controller_pairing_screen.h b/chrome/browser/chromeos/login/screens/controller_pairing_screen.h
index 6dcfa18..9305e14 100644
--- a/chrome/browser/chromeos/login/screens/controller_pairing_screen.h
+++ b/chrome/browser/chromeos/login/screens/controller_pairing_screen.h
@@ -46,7 +46,6 @@
   // Overridden from BaseScreen:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // Overridden from pairing_chromeos::ControllerPairingController::Observer:
   void PairingStageChanged(Stage new_stage) override;
diff --git a/chrome/browser/chromeos/login/screens/device_disabled_screen.cc b/chrome/browser/chromeos/login/screens/device_disabled_screen.cc
index 5c51620f..d5ca43e 100644
--- a/chrome/browser/chromeos/login/screens/device_disabled_screen.cc
+++ b/chrome/browser/chromeos/login/screens/device_disabled_screen.cc
@@ -16,10 +16,11 @@
 DeviceDisabledScreen::DeviceDisabledScreen(
     BaseScreenDelegate* base_screen_delegate,
     DeviceDisabledScreenActor* actor)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kDeviceDisabledScreenName),
       actor_(actor),
-      device_disabling_manager_(g_browser_process->platform_part()->
-                                    device_disabling_manager()),
+      device_disabling_manager_(
+          g_browser_process->platform_part()->device_disabling_manager()),
       showing_(false) {
   DCHECK(actor_);
   if (actor_)
@@ -50,10 +51,6 @@
     actor_->Hide();
 }
 
-std::string DeviceDisabledScreen::GetName() const {
-  return WizardController::kDeviceDisabledScreenName;
-}
-
 void DeviceDisabledScreen::OnActorDestroyed(DeviceDisabledScreenActor* actor) {
   if (actor_ == actor)
     actor_ = nullptr;
diff --git a/chrome/browser/chromeos/login/screens/device_disabled_screen.h b/chrome/browser/chromeos/login/screens/device_disabled_screen.h
index ad1004d..6aff9bb 100644
--- a/chrome/browser/chromeos/login/screens/device_disabled_screen.h
+++ b/chrome/browser/chromeos/login/screens/device_disabled_screen.h
@@ -30,7 +30,6 @@
   // BaseScreen:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // DeviceDisabledScreenActor::Delegate:
   void OnActorDestroyed(DeviceDisabledScreenActor* actor) override;
diff --git a/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc b/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
index a4e605e..babfbdf4 100644
--- a/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
+++ b/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
@@ -10,10 +10,10 @@
 
 namespace chromeos {
 
-EnableDebuggingScreen::EnableDebuggingScreen(
-    BaseScreenDelegate* delegate,
-    EnableDebuggingScreenActor* actor)
-    : BaseScreen(delegate), actor_(actor) {
+EnableDebuggingScreen::EnableDebuggingScreen(BaseScreenDelegate* delegate,
+                                             EnableDebuggingScreenActor* actor)
+    : BaseScreen(delegate, WizardController::kEnableDebuggingScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -34,10 +34,6 @@
     actor_->Hide();
 }
 
-std::string EnableDebuggingScreen::GetName() const {
-  return WizardController::kEnableDebuggingScreenName;
-}
-
 void EnableDebuggingScreen::OnExit(bool success) {
   Finish(success ? BaseScreenDelegate::ENABLE_DEBUGGING_FINISHED :
                    BaseScreenDelegate::ENABLE_DEBUGGING_CANCELED);
diff --git a/chrome/browser/chromeos/login/screens/enable_debugging_screen.h b/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
index 49cad4b8..36db96b1 100644
--- a/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
+++ b/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
@@ -25,7 +25,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // EnableDebuggingScreenActor::Delegate implementation:
   void OnExit(bool success) override;
diff --git a/chrome/browser/chromeos/login/screens/eula_model.cc b/chrome/browser/chromeos/login/screens/eula_model.cc
index 12dca310..e8f0119 100644
--- a/chrome/browser/chromeos/login/screens/eula_model.cc
+++ b/chrome/browser/chromeos/login/screens/eula_model.cc
@@ -13,14 +13,9 @@
 const char EulaModel::kContextKeyUsageStatsEnabled[] = "usageStatsEnabled";
 
 EulaModel::EulaModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate, WizardController::kEulaScreenName) {}
 
 EulaModel::~EulaModel() {
 }
 
-std::string EulaModel::GetName() const {
-  return WizardController::kEulaScreenName;
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/eula_model.h b/chrome/browser/chromeos/login/screens/eula_model.h
index 121d1b76..fb1a45a 100644
--- a/chrome/browser/chromeos/login/screens/eula_model.h
+++ b/chrome/browser/chromeos/login/screens/eula_model.h
@@ -23,9 +23,6 @@
   explicit EulaModel(BaseScreenDelegate* base_screen_delegate);
   ~EulaModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // Returns URL of the OEM EULA page that should be displayed using current
   // locale and manifest. Returns empty URL otherwise.
   virtual GURL GetOemEulaUrl() const = 0;
diff --git a/chrome/browser/chromeos/login/screens/hid_detection_model.cc b/chrome/browser/chromeos/login/screens/hid_detection_model.cc
index 5f30798..65caf93 100644
--- a/chrome/browser/chromeos/login/screens/hid_detection_model.cc
+++ b/chrome/browser/chromeos/login/screens/hid_detection_model.cc
@@ -25,14 +25,10 @@
     "continue-button-enabled";
 
 HIDDetectionModel::HIDDetectionModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kHIDDetectionScreenName) {}
 
 HIDDetectionModel::~HIDDetectionModel() {
 }
 
-std::string HIDDetectionModel::GetName() const {
-  return WizardController::kHIDDetectionScreenName;
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/hid_detection_model.h b/chrome/browser/chromeos/login/screens/hid_detection_model.h
index 598992a3..2e5fa30 100644
--- a/chrome/browser/chromeos/login/screens/hid_detection_model.h
+++ b/chrome/browser/chromeos/login/screens/hid_detection_model.h
@@ -28,9 +28,6 @@
   explicit HIDDetectionModel(BaseScreenDelegate* base_screen_delegate);
   ~HIDDetectionModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // Called when continue button was clicked.
   virtual void OnContinueButtonClicked() = 0;
 
diff --git a/chrome/browser/chromeos/login/screens/host_pairing_screen.cc b/chrome/browser/chromeos/login/screens/host_pairing_screen.cc
index 93136a8..ad95e39 100644
--- a/chrome/browser/chromeos/login/screens/host_pairing_screen.cc
+++ b/chrome/browser/chromeos/login/screens/host_pairing_screen.cc
@@ -22,7 +22,8 @@
     Delegate* delegate,
     HostPairingScreenActor* actor,
     pairing_chromeos::HostPairingController* remora_controller)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kHostPairingScreenName),
       delegate_(delegate),
       actor_(actor),
       remora_controller_(remora_controller),
@@ -58,10 +59,6 @@
     actor_->Hide();
 }
 
-std::string HostPairingScreen::GetName() const {
-  return WizardController::kHostPairingScreenName;
-}
-
 void HostPairingScreen::PairingStageChanged(Stage new_stage) {
   std::string desired_page;
   switch (new_stage) {
diff --git a/chrome/browser/chromeos/login/screens/host_pairing_screen.h b/chrome/browser/chromeos/login/screens/host_pairing_screen.h
index ed03abf..b209d5d 100644
--- a/chrome/browser/chromeos/login/screens/host_pairing_screen.h
+++ b/chrome/browser/chromeos/login/screens/host_pairing_screen.h
@@ -51,7 +51,6 @@
   // Overridden from BaseScreen:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // pairing_chromeos::HostPairingController::Observer:
   void PairingStageChanged(Stage new_stage) override;
diff --git a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
index 733dad7e..047bca2 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
+++ b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
@@ -14,7 +14,9 @@
 KioskAutolaunchScreen::KioskAutolaunchScreen(
     BaseScreenDelegate* base_screen_delegate,
     KioskAutolaunchScreenActor* actor)
-    : BaseScreen(base_screen_delegate), actor_(actor) {
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kKioskAutolaunchScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -30,10 +32,6 @@
     actor_->Show();
 }
 
-std::string KioskAutolaunchScreen::GetName() const {
-  return WizardController::kKioskAutolaunchScreenName;
-}
-
 void KioskAutolaunchScreen::OnExit(bool confirmed) {
   Finish(confirmed ? BaseScreenDelegate::KIOSK_AUTOLAUNCH_CONFIRMED
                    : BaseScreenDelegate::KIOSK_AUTOLAUNCH_CANCELED);
diff --git a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
index e5e01b4..5a22e59 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
+++ b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
@@ -26,7 +26,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override {}
-  std::string GetName() const override;
 
   // KioskAutolaunchScreenActor::Delegate implementation:
   void OnExit(bool confirmed) override;
diff --git a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
index 1a285a9..3311bd9 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
+++ b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
@@ -13,7 +13,9 @@
 
 KioskEnableScreen::KioskEnableScreen(BaseScreenDelegate* base_screen_delegate,
                                      KioskEnableScreenActor* actor)
-    : BaseScreen(base_screen_delegate), actor_(actor) {
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kKioskEnableScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -29,10 +31,6 @@
     actor_->Show();
 }
 
-std::string KioskEnableScreen::GetName() const {
-  return WizardController::kKioskEnableScreenName;
-}
-
 void KioskEnableScreen::OnExit() {
   Finish(BaseScreenDelegate::KIOSK_ENABLE_COMPLETED);
 }
diff --git a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
index baad510b..e5edae3 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
+++ b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
@@ -26,7 +26,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override {}
-  std::string GetName() const override;
 
   // KioskEnableScreenActor::Delegate implementation:
   void OnExit() override;
diff --git a/chrome/browser/chromeos/login/screens/mock_eula_screen.h b/chrome/browser/chromeos/login/screens/mock_eula_screen.h
index 59743422..27c4ba4 100644
--- a/chrome/browser/chromeos/login/screens/mock_eula_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_eula_screen.h
@@ -31,8 +31,6 @@
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
 
-  MOCK_CONST_METHOD0(GetName, std::string());
-
   MOCK_METHOD1(MockBind, void(EulaModel& model));
   MOCK_METHOD0(MockUnbind, void());
   MOCK_METHOD1(OnPasswordFetched, void(const std::string& tpm_password));
diff --git a/chrome/browser/chromeos/login/screens/network_error_model.cc b/chrome/browser/chromeos/login/screens/network_error_model.cc
index be02d01..b1d3d7bc 100644
--- a/chrome/browser/chromeos/login/screens/network_error_model.cc
+++ b/chrome/browser/chromeos/login/screens/network_error_model.cc
@@ -33,14 +33,8 @@
     "connect-requested";
 
 NetworkErrorModel::NetworkErrorModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate, WizardController::kErrorScreenName) {}
 
-NetworkErrorModel::~NetworkErrorModel() {
-}
-
-std::string NetworkErrorModel::GetName() const {
-  return WizardController::kErrorScreenName;
-}
+NetworkErrorModel::~NetworkErrorModel() {}
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_error_model.h b/chrome/browser/chromeos/login/screens/network_error_model.h
index 81941f3..ba201b3 100644
--- a/chrome/browser/chromeos/login/screens/network_error_model.h
+++ b/chrome/browser/chromeos/login/screens/network_error_model.h
@@ -35,9 +35,6 @@
   explicit NetworkErrorModel(BaseScreenDelegate* base_screen_delegate);
   ~NetworkErrorModel() override;
 
-  // BaseScreen:
-  std::string GetName() const override;
-
   // Toggles the guest sign-in prompt.
   virtual void AllowGuestSignin(bool allowed) = 0;
 
diff --git a/chrome/browser/chromeos/login/screens/network_model.cc b/chrome/browser/chromeos/login/screens/network_model.cc
index fbff3b2..36c4b8cc 100644
--- a/chrome/browser/chromeos/login/screens/network_model.cc
+++ b/chrome/browser/chromeos/login/screens/network_model.cc
@@ -18,14 +18,8 @@
     "continue-button-enabled";
 
 NetworkModel::NetworkModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate, WizardController::kNetworkScreenName) {}
 
-NetworkModel::~NetworkModel() {
-}
-
-std::string NetworkModel::GetName() const {
-  return WizardController::kNetworkScreenName;
-}
+NetworkModel::~NetworkModel() {}
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_model.h b/chrome/browser/chromeos/login/screens/network_model.h
index 8e3035f..66cbfda 100644
--- a/chrome/browser/chromeos/login/screens/network_model.h
+++ b/chrome/browser/chromeos/login/screens/network_model.h
@@ -28,9 +28,6 @@
   explicit NetworkModel(BaseScreenDelegate* base_screen_delegate);
   ~NetworkModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // This method is called, when view is being destroyed. Note, if model
   // is destroyed earlier then it has to call Unbind().
   virtual void OnViewDestroyed(NetworkView* view) = 0;
diff --git a/chrome/browser/chromeos/login/screens/reset_model.cc b/chrome/browser/chromeos/login/screens/reset_model.cc
index 48ae3a00..fcaf183e8 100644
--- a/chrome/browser/chromeos/login/screens/reset_model.cc
+++ b/chrome/browser/chromeos/login/screens/reset_model.cc
@@ -26,14 +26,8 @@
 const char ResetModel::kContextKeyScreenState[] = "screen-state";
 
 ResetModel::ResetModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate, WizardController::kResetScreenName) {}
 
-ResetModel::~ResetModel() {
-}
-
-std::string ResetModel::GetName() const {
-  return WizardController::kResetScreenName;
-}
+ResetModel::~ResetModel() {}
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/reset_model.h b/chrome/browser/chromeos/login/screens/reset_model.h
index 75d11c56..5e37046 100644
--- a/chrome/browser/chromeos/login/screens/reset_model.h
+++ b/chrome/browser/chromeos/login/screens/reset_model.h
@@ -33,9 +33,6 @@
   explicit ResetModel(BaseScreenDelegate* base_screen_delegate);
   ~ResetModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // Called when actor is destroyed so there's no dead reference to it.
   virtual void OnViewDestroyed(ResetView* view) = 0;
 };
diff --git a/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc b/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
index 5595a5fe..b121070 100644
--- a/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
+++ b/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
@@ -29,7 +29,9 @@
 TermsOfServiceScreen::TermsOfServiceScreen(
     BaseScreenDelegate* base_screen_delegate,
     TermsOfServiceScreenActor* actor)
-    : BaseScreen(base_screen_delegate), actor_(actor) {
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kTermsOfServiceScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -61,10 +63,6 @@
     actor_->Hide();
 }
 
-std::string TermsOfServiceScreen::GetName() const {
-  return WizardController::kTermsOfServiceScreenName;
-}
-
 void TermsOfServiceScreen::OnDecline() {
   Finish(BaseScreenDelegate::TERMS_OF_SERVICE_DECLINED);
 }
diff --git a/chrome/browser/chromeos/login/screens/terms_of_service_screen.h b/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
index 26f26c6..d4fff4da 100644
--- a/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
+++ b/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
@@ -37,7 +37,6 @@
   // BaseScreen:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // TermsOfServiceScreenActor::Delegate:
   void OnDecline() override;
diff --git a/chrome/browser/chromeos/login/screens/update_model.cc b/chrome/browser/chromeos/login/screens/update_model.cc
index d8f2be49..61d21e2 100644
--- a/chrome/browser/chromeos/login/screens/update_model.cc
+++ b/chrome/browser/chromeos/login/screens/update_model.cc
@@ -20,14 +20,8 @@
     "cancel-update-enabled";
 
 UpdateModel::UpdateModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
-}
+    : BaseScreen(base_screen_delegate, WizardController::kUpdateScreenName) {}
 
-UpdateModel::~UpdateModel() {
-}
-
-std::string UpdateModel::GetName() const {
-  return WizardController::kUpdateScreenName;
-}
+UpdateModel::~UpdateModel() {}
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/update_model.h b/chrome/browser/chromeos/login/screens/update_model.h
index af18b50..2a8a7ed2 100644
--- a/chrome/browser/chromeos/login/screens/update_model.h
+++ b/chrome/browser/chromeos/login/screens/update_model.h
@@ -27,9 +27,6 @@
   explicit UpdateModel(BaseScreenDelegate* base_screen_delegate);
   ~UpdateModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // This method is called, when view is being destroyed. Note, if model
   // is destroyed earlier then it has to call Unbind().
   virtual void OnViewDestroyed(UpdateView* view) = 0;
diff --git a/chrome/browser/chromeos/login/screens/update_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/update_screen_browsertest.cc
index d08ad8c..24f8643 100644
--- a/chrome/browser/chromeos/login/screens/update_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/update_screen_browsertest.cc
@@ -182,7 +182,8 @@
   // Check that OOBE will resume back at this screen.
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(StartupUtils::IsOobeCompleted());
-  EXPECT_EQ(update_screen_->GetName(),
+  EXPECT_EQ(
+      update_screen_->screen_id(),
       g_browser_process->local_state()->GetString(prefs::kOobeScreenPending));
 }
 
diff --git a/chrome/browser/chromeos/login/screens/user_image_model.cc b/chrome/browser/chromeos/login/screens/user_image_model.cc
index 6a90d4aa..475d87a9 100644
--- a/chrome/browser/chromeos/login/screens/user_image_model.cc
+++ b/chrome/browser/chromeos/login/screens/user_image_model.cc
@@ -14,14 +14,9 @@
 const char UserImageModel::kContextKeySelectedImageURL[] = "selectedImageURL";
 
 UserImageModel::UserImageModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate) {
+    : BaseScreen(base_screen_delegate, WizardController::kUserImageScreenName) {
 }
 
-UserImageModel::~UserImageModel() {
-}
-
-std::string UserImageModel::GetName() const {
-  return WizardController::kUserImageScreenName;
-}
+UserImageModel::~UserImageModel() {}
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/user_image_model.h b/chrome/browser/chromeos/login/screens/user_image_model.h
index 6234475..9e9a0617 100644
--- a/chrome/browser/chromeos/login/screens/user_image_model.h
+++ b/chrome/browser/chromeos/login/screens/user_image_model.h
@@ -23,9 +23,6 @@
   explicit UserImageModel(BaseScreenDelegate* base_screen_delegate);
   ~UserImageModel() override;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // Called when UI ready to be shown.
   virtual void OnScreenReady() = 0;
 
diff --git a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
index 661b0d25..4cf6062b 100644
--- a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
+++ b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
@@ -11,7 +11,8 @@
 
 WrongHWIDScreen::WrongHWIDScreen(BaseScreenDelegate* base_screen_delegate,
                                  WrongHWIDScreenActor* actor)
-    : BaseScreen(base_screen_delegate), actor_(actor) {
+    : BaseScreen(base_screen_delegate, WizardController::kWrongHWIDScreenName),
+      actor_(actor) {
   DCHECK(actor_);
   if (actor_)
     actor_->SetDelegate(this);
@@ -32,10 +33,6 @@
     actor_->Hide();
 }
 
-std::string WrongHWIDScreen::GetName() const {
-  return WizardController::kWrongHWIDScreenName;
-}
-
 void WrongHWIDScreen::OnExit() {
   Finish(BaseScreenDelegate::WRONG_HWID_WARNING_SKIPPED);
 }
diff --git a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
index 55e486ae..ae1fe959 100644
--- a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
+++ b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
@@ -26,7 +26,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // WrongHWIDScreenActor::Delegate implementation:
   void OnExit() override;
diff --git a/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.cc b/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.cc
index 36d99c6..82b07b4 100644
--- a/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.cc
+++ b/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.cc
@@ -103,7 +103,8 @@
 SupervisedUserCreationScreen::SupervisedUserCreationScreen(
     BaseScreenDelegate* base_screen_delegate,
     SupervisedUserCreationScreenHandler* actor)
-    : BaseScreen(base_screen_delegate),
+    : BaseScreen(base_screen_delegate,
+                 WizardController::kSupervisedUserCreationScreenName),
       actor_(actor),
       on_error_screen_(false),
       manager_signin_in_progress_(false),
@@ -192,10 +193,6 @@
     network_portal_detector::GetInstance()->RemoveObserver(this);
 }
 
-std::string SupervisedUserCreationScreen::GetName() const {
-  return WizardController::kSupervisedUserCreationScreenName;
-}
-
 void SupervisedUserCreationScreen::AbortFlow() {
   DBusThreadManager::Get()
       ->GetSessionManagerClient()
diff --git a/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.h b/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.h
index e123bb0..cc79735 100644
--- a/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.h
+++ b/chrome/browser/chromeos/login/supervised/supervised_user_creation_screen.h
@@ -77,7 +77,6 @@
   // BaseScreen implementation:
   void Show() override;
   void Hide() override;
-  std::string GetName() const override;
 
   // SupervisedUserCreationScreenHandler::Delegate implementation:
   void OnActorDestroyed(SupervisedUserCreationScreenHandler* actor) override;
diff --git a/chrome/browser/chromeos/login/ui/models/user_board_model.cc b/chrome/browser/chromeos/login/ui/models/user_board_model.cc
index fd90a9216..c693e0c 100644
--- a/chrome/browser/chromeos/login/ui/models/user_board_model.cc
+++ b/chrome/browser/chromeos/login/ui/models/user_board_model.cc
@@ -10,12 +10,8 @@
 
 namespace chromeos {
 
-UserBoardModel::UserBoardModel() : BaseScreen(nullptr) {}
+UserBoardModel::UserBoardModel() : BaseScreen(nullptr, kUserBoardScreenName) {}
 
 UserBoardModel::~UserBoardModel() {}
 
-std::string UserBoardModel::GetName() const {
-  return kUserBoardScreenName;
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/models/user_board_model.h b/chrome/browser/chromeos/login/ui/models/user_board_model.h
index ebbebdd1..11916a7 100644
--- a/chrome/browser/chromeos/login/ui/models/user_board_model.h
+++ b/chrome/browser/chromeos/login/ui/models/user_board_model.h
@@ -24,9 +24,6 @@
   virtual void AttemptEasyUnlock(const AccountId& account_id) = 0;
   virtual void RecordClickOnLockIcon(const AccountId& account_id) = 0;
 
-  // BaseScreen implementation:
-  std::string GetName() const override;
-
   // Temorary unused methods:
   void Show() override{};
   void Hide() override{};
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 3e572c9..d5b71add 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -958,8 +958,8 @@
     return;
 
   // First remember how far have we reached so that we can resume if needed.
-  if (is_out_of_box_ && IsResumableScreen(current_screen_->GetName()))
-    StartupUtils::SaveOobePendingScreen(current_screen_->GetName());
+  if (is_out_of_box_ && IsResumableScreen(current_screen_->screen_id()))
+    StartupUtils::SaveOobePendingScreen(current_screen_->screen_id());
 
   smooth_show_timer_.Stop();
 
@@ -981,7 +981,7 @@
   if (current_screen_)
     current_screen_->Hide();
 
-  std::string screen_id = new_current->GetName();
+  const std::string screen_id = new_current->screen_id();
   if (IsOOBEStepToTrack(screen_id))
     screen_show_times_[screen_id] = base::Time::Now();
 
@@ -1085,7 +1085,7 @@
                               ExitCodes exit_code,
                               const ::login::ScreenContext* /* context */) {
   VLOG(1) << "Wizard screen exit code: " << exit_code;
-  std::string previous_screen_id = current_screen_->GetName();
+  const std::string previous_screen_id = current_screen_->screen_id();
   if (IsOOBEStepToTrack(previous_screen_id)) {
     RecordUMAHistogramForOOBEStepCompletionTime(
         previous_screen_id,
diff --git a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
index 565c71c..acdf59d 100644
--- a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
@@ -2156,7 +2156,7 @@
   ASSERT_TRUE(wizard_controller);
   ASSERT_TRUE(wizard_controller->current_screen());
   EXPECT_EQ(chromeos::WizardController::kTermsOfServiceScreenName,
-            wizard_controller->current_screen()->GetName());
+            wizard_controller->current_screen()->screen_id());
 
   // Wait for the Terms of Service to finish downloading.
   bool done = false;
@@ -2363,7 +2363,7 @@
   ASSERT_TRUE(wizard_controller);
   ASSERT_TRUE(wizard_controller->current_screen());
   EXPECT_EQ(chromeos::WizardController::kTermsOfServiceScreenName,
-            wizard_controller->current_screen()->GetName());
+            wizard_controller->current_screen()->screen_id());
 
   // Wait for the Terms of Service to finish downloading, then get the status of
   // the screen's UI elements.
diff --git a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
index 98b9d286..2e0de2e4 100644
--- a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
+++ b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
@@ -24,6 +24,7 @@
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/metrics/chromeos_metrics_provider.h"
 #include "chromeos/system/statistics_provider.h"
+#include "chromeos/system/version_loader.h"
 #endif
 
 #if defined(OS_WIN)
@@ -39,6 +40,7 @@
 constexpr char kDataReductionProxyKey[] = "data_reduction_proxy";
 constexpr char kChromeVersionTag[] = "CHROME VERSION";
 #if defined(OS_CHROMEOS)
+constexpr char kChromeOsFirmwareVersion[] = "CHROMEOS_FIRMWARE_VERSION";
 constexpr char kChromeEnrollmentTag[] = "ENTERPRISE_ENROLLED";
 constexpr char kHWIDKey[] = "HWID";
 constexpr char kSettingsKey[] = "settings";
@@ -69,20 +71,23 @@
   return std::string();
 }
 
-void GetHWID(SystemLogsResponse* response) {
+void GetEntriesOnBlockingPool(SystemLogsResponse* response) {
   DCHECK(response);
 
   chromeos::system::StatisticsProvider* stats =
       chromeos::system::StatisticsProvider::GetInstance();
   DCHECK(stats);
 
+  // Get the HWID.
   std::string hwid;
-  if (!stats->GetMachineStatistic(chromeos::system::kHardwareClassKey, &hwid)) {
+  if (!stats->GetMachineStatistic(chromeos::system::kHardwareClassKey, &hwid))
     VLOG(1) << "Couldn't get machine statistic 'hardware_class'.";
-    return;
-  }
+  else
+    (*response)[kHWIDKey] = hwid;
 
-  (*response)[kHWIDKey] = hwid;
+  // Get the firmware version.
+  (*response)[kChromeOsFirmwareVersion] =
+      chromeos::version_loader::GetFirmware();
 }
 #endif
 
@@ -127,10 +132,11 @@
 #if defined(OS_CHROMEOS)
   PopulateLocalStateSettings(response.get());
 
-  // Get the HWID on the blocking pool and invoke the callback later when done.
+  // Get the entries that should be retrieved on the blocking pool and invoke
+  // the callback later when done.
   SystemLogsResponse* response_ptr = response.release();
   content::BrowserThread::PostBlockingPoolTaskAndReply(
-      FROM_HERE, base::Bind(&GetHWID, response_ptr),
+      FROM_HERE, base::Bind(&GetEntriesOnBlockingPool, response_ptr),
       base::Bind(callback, base::Owned(response_ptr)));
 #else
   // On other platforms, we're done. Invoke the callback.
diff --git a/chrome/browser/safe_browsing/settings_reset_prompt/OWNERS b/chrome/browser/safe_browsing/settings_reset_prompt/OWNERS
new file mode 100644
index 0000000..93d2314
--- /dev/null
+++ b/chrome/browser/safe_browsing/settings_reset_prompt/OWNERS
@@ -0,0 +1,2 @@
+csharp@chromium.org
+robertshield@chromium.org
diff --git a/chrome/browser/ui/android/page_info/connection_info_popup_android.cc b/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
index c7254a2..5bfa205 100644
--- a/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
+++ b/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
@@ -156,12 +156,6 @@
   NOTIMPLEMENTED();
 }
 
-void ConnectionInfoPopupAndroid::SetSelectedTab(
-    WebsiteSettingsUI::TabId tab_id) {
-  // There's no tab UI on Android - only connection info is shown.
-  NOTIMPLEMENTED();
-}
-
 // static
 bool
 ConnectionInfoPopupAndroid::RegisterConnectionInfoPopupAndroid(
diff --git a/chrome/browser/ui/android/page_info/connection_info_popup_android.h b/chrome/browser/ui/android/page_info/connection_info_popup_android.h
index 97bf78e..52f04f4 100644
--- a/chrome/browser/ui/android/page_info/connection_info_popup_android.h
+++ b/chrome/browser/ui/android/page_info/connection_info_popup_android.h
@@ -39,7 +39,6 @@
   void SetPermissionInfo(const PermissionInfoList& permission_info_list,
                          ChosenObjectInfoList chosen_object_info_list) override;
   void SetIdentityInfo(const IdentityInfo& identity_info) override;
-  void SetSelectedTab(WebsiteSettingsUI::TabId tab_id) override;
 
   static bool RegisterConnectionInfoPopupAndroid(JNIEnv* env);
 
diff --git a/chrome/browser/ui/android/page_info/website_settings_popup_android.cc b/chrome/browser/ui/android/page_info/website_settings_popup_android.cc
index 40528df..b63da42 100644
--- a/chrome/browser/ui/android/page_info/website_settings_popup_android.cc
+++ b/chrome/browser/ui/android/page_info/website_settings_popup_android.cc
@@ -148,12 +148,6 @@
   Java_WebsiteSettingsPopup_updatePermissionDisplay(env, popup_jobject_);
 }
 
-void WebsiteSettingsPopupAndroid::SetSelectedTab(
-    WebsiteSettingsUI::TabId tab_id) {
-  // There's no tab UI on Android - only connection info is shown.
-  NOTIMPLEMENTED();
-}
-
 // static
 bool WebsiteSettingsPopupAndroid::RegisterWebsiteSettingsPopupAndroid(
     JNIEnv* env) {
diff --git a/chrome/browser/ui/android/page_info/website_settings_popup_android.h b/chrome/browser/ui/android/page_info/website_settings_popup_android.h
index 46a27b78b..066d87b 100644
--- a/chrome/browser/ui/android/page_info/website_settings_popup_android.h
+++ b/chrome/browser/ui/android/page_info/website_settings_popup_android.h
@@ -46,7 +46,6 @@
   void SetPermissionInfo(const PermissionInfoList& permission_info_list,
                          ChosenObjectInfoList chosen_object_info_list) override;
   void SetIdentityInfo(const IdentityInfo& identity_info) override;
-  void SetSelectedTab(WebsiteSettingsUI::TabId tab_id) override;
 
   static bool RegisterWebsiteSettingsPopupAndroid(JNIEnv* env);
 
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.cc b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
index 7b992ac..ce01953 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
@@ -210,9 +210,14 @@
 const char kPlayStorePackage[] = "com.android.vending";
 const char kPlayStoreActivity[] = "com.android.vending.AssetBrowserActivity";
 const char kSettingsAppId[] = "mconboelelhjpkbdhhiijkgcimoangdj";
+const char kAndroidWallpapersAppPackage[] = "com.google.android.apps.wallpaper";
+const char kAndroidWallpapersAppActivity[] =
+    "com.google.android.apps.wallpaper.picker.CategoryPickerActivity";
 
 bool ShouldShowInLauncher(const std::string& app_id) {
-  return (app_id != kSettingsAppId);
+  const std::string wallpapers_app_id = ArcAppListPrefs::GetAppId(
+      kAndroidWallpapersAppPackage, kAndroidWallpapersAppActivity);
+  return (app_id != kSettingsAppId) && (app_id != wallpapers_app_id);
 }
 
 bool LaunchAppWithRect(content::BrowserContext* context,
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.h b/chrome/browser/ui/app_list/arc/arc_app_utils.h
index e60f40f5..f0caf8c 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.h
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.h
@@ -23,6 +23,8 @@
 extern const char kPlayStorePackage[];
 extern const char kPlayStoreActivity[];
 extern const char kSettingsAppId[];
+extern const char kAndroidWallpapersAppPackage[];
+extern const char kAndroidWallpapersAppActivity[];
 
 using CanHandleResolutionCallback = base::Callback<void(bool)>;
 
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 767d3607..b64ad91 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -574,7 +574,19 @@
 
 base::string16 Browser::GetWindowTitleForCurrentTab(
     bool include_app_name) const {
-  WebContents* contents = tab_strip_model_->GetActiveWebContents();
+  return GetWindowTitleFromWebContents(
+      include_app_name, tab_strip_model_->GetActiveWebContents());
+}
+
+base::string16 Browser::GetWindowTitleForTab(bool include_app_name,
+                                             int index) const {
+  return GetWindowTitleFromWebContents(
+      include_app_name, tab_strip_model_->GetWebContentsAt(index));
+}
+
+base::string16 Browser::GetWindowTitleFromWebContents(
+    bool include_app_name,
+    content::WebContents* contents) const {
   base::string16 title;
 
   // |contents| can be NULL because GetWindowTitleForCurrentTab is called by the
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 1e6c596..0860714 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -42,6 +42,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/page_navigator.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/page_zoom.h"
 #include "extensions/features/features.h"
@@ -299,6 +300,16 @@
   // Disables additional formatting when |include_app_name| is false.
   base::string16 GetWindowTitleForCurrentTab(bool include_app_name) const;
 
+  // Gets the window title of the tab at |index|.
+  // Disables additional formatting when |include_app_name| is false.
+  base::string16 GetWindowTitleForTab(bool include_app_name, int index) const;
+
+  // Gets the window title from the provided WebContents.
+  // Disables additional formatting when |include_app_name| is false.
+  base::string16 GetWindowTitleFromWebContents(
+      bool include_app_name,
+      content::WebContents* contents) const;
+
   // Prepares a title string for display (removes embedded newlines, etc).
   static void FormatTitleForDisplay(base::string16* title);
 
diff --git a/chrome/browser/ui/cocoa/download/download_item_cell.mm b/chrome/browser/ui/cocoa/download/download_item_cell.mm
index 74b1962..9744ffc1 100644
--- a/chrome/browser/ui/cocoa/download/download_item_cell.mm
+++ b/chrome/browser/ui/cocoa/download/download_item_cell.mm
@@ -20,6 +20,7 @@
 #include "ui/gfx/canvas_skia_paint.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/shadow_value.h"
 #include "ui/gfx/text_elider.h"
 #include "ui/native_theme/native_theme.h"
 
diff --git a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.h b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.h
index f59a72bf..721237d 100644
--- a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.h
+++ b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.h
@@ -141,7 +141,6 @@
   void SetPermissionInfo(const PermissionInfoList& permission_info_list,
                          ChosenObjectInfoList chosen_object_info_list) override;
   void SetIdentityInfo(const IdentityInfo& identity_info) override;
-  void SetSelectedTab(TabId tab_id) override;
 
  private:
   // The WebContents the bubble UI is attached to.
diff --git a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm
index c978786..31b9d3c 100644
--- a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm
+++ b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm
@@ -1216,7 +1216,3 @@
   [bubble_controller_ setPermissionInfo:permission_info_list
                        andChosenObjects:std::move(chosen_object_info_list)];
 }
-
-void WebsiteSettingsUIBridge::SetSelectedTab(TabId tab_id) {
-  // TODO(lgarron): Remove this from the interface. (crbug.com/571533)
-}
diff --git a/chrome/browser/ui/libgtkui/gtk_ui.cc b/chrome/browser/ui/libgtkui/gtk_ui.cc
index 9c52094..766da80 100644
--- a/chrome/browser/ui/libgtkui/gtk_ui.cc
+++ b/chrome/browser/ui/libgtkui/gtk_ui.cc
@@ -880,7 +880,7 @@
 #if GTK_MAJOR_VERSION > 2
   colors_[ThemeProperties::COLOR_TOOLBAR_BOTTOM_SEPARATOR] =
       colors_[ThemeProperties::COLOR_DETACHED_BOOKMARK_BAR_SEPARATOR] =
-          GetBorderColor("GtkToolbar.primary-toolbar.toolbar");
+          GetBorderColor("GtkToolbar#toolbar.primary-toolbar");
 #endif
 }
 
@@ -925,9 +925,9 @@
   colors_[ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE] = temp_color;
 #else
   // TODO(thomasanderson): Render a GtkHeaderBar directly.
-  SkColor color_frame = GetBgColor(".headerbar.header-bar.titlebar");
+  SkColor color_frame = GetBgColor("#headerbar.header-bar.titlebar");
   SkColor color_frame_inactive =
-      GetBgColor(".headerbar.header-bar.titlebar:backdrop");
+      GetBgColor("#headerbar.header-bar.titlebar:backdrop");
   colors_[ThemeProperties::COLOR_FRAME] = color_frame;
   colors_[ThemeProperties::COLOR_FRAME_INACTIVE] = color_frame_inactive;
   colors_[ThemeProperties::COLOR_FRAME_INCOGNITO] =
diff --git a/chrome/browser/ui/libgtkui/gtk_util.cc b/chrome/browser/ui/libgtkui/gtk_util.cc
index 53a370f..826af71 100644
--- a/chrome/browser/ui/libgtkui/gtk_util.cc
+++ b/chrome/browser/ui/libgtkui/gtk_util.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/libgtkui/gtk_util.h"
 
+#include <dlfcn.h>
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #include <gtk/gtk.h>
@@ -52,6 +53,14 @@
   }
 }
 
+#if GTK_MAJOR_VERSION == 3
+void* GetGtk3SharedLibrary() {
+  static void* gtk3lib = dlopen("libgtk-3.so.0", RTLD_LAZY);
+  DCHECK(gtk3lib);
+  return gtk3lib;
+}
+#endif
+
 }  // namespace
 
 namespace libgtkui {
@@ -199,8 +208,8 @@
               : gtk_widget_path_new();
 
   enum {
-    // TODO(thomasanderson): Add CSS_NAME here to handle the Gtk3.20 case.
     CSS_TYPE,
+    CSS_NAME,
     CSS_CLASS,
     CSS_PSEUDOCLASS,
   } part_type = CSS_TYPE;
@@ -224,7 +233,7 @@
   };
   GtkStateFlags state =
       context ? gtk_style_context_get_state(context) : GTK_STATE_FLAG_NORMAL;
-  base::StringTokenizer t(css_node, ".:");
+  base::StringTokenizer t(css_node, ".:#");
   t.set_options(base::StringTokenizer::RETURN_DELIMS);
   while (t.GetNext()) {
     if (t.token_is_delim()) {
@@ -233,6 +242,9 @@
         gtk_widget_path_append_type(path, G_TYPE_NONE);
       }
       switch (*t.token_begin()) {
+        case '#':
+          part_type = CSS_NAME;
+          break;
         case '.':
           part_type = CSS_CLASS;
           break;
@@ -244,6 +256,20 @@
       }
     } else {
       switch (part_type) {
+        case CSS_NAME: {
+          if (gtk_get_major_version() > 3 ||
+              (gtk_get_major_version() == 3 && gtk_get_minor_version() >= 20)) {
+            static auto* _gtk_widget_path_iter_set_object_name =
+                reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
+                    dlsym(GetGtk3SharedLibrary(),
+                          "gtk_widget_path_iter_set_object_name"));
+            DCHECK(_gtk_widget_path_iter_set_object_name);
+            _gtk_widget_path_iter_set_object_name(path, -1, t.token().c_str());
+          } else {
+            gtk_widget_path_iter_add_class(path, -1, t.token().c_str());
+          }
+          break;
+        }
         case CSS_TYPE: {
           GType type = g_type_from_name(t.token().c_str());
           DCHECK(type);
diff --git a/chrome/browser/ui/libgtkui/native_theme_gtk3.cc b/chrome/browser/ui/libgtkui/native_theme_gtk3.cc
index 852ed8a..84de8bd 100644
--- a/chrome/browser/ui/libgtkui/native_theme_gtk3.cc
+++ b/chrome/browser/ui/libgtkui/native_theme_gtk3.cc
@@ -74,44 +74,44 @@
 
     // FocusableBorder
     case ui::NativeTheme::kColorId_FocusedBorderColor:
-      return GetBorderColor("GtkEntry.entry:focus");
+      return GetBorderColor("GtkEntry#entry:focus");
     case ui::NativeTheme::kColorId_UnfocusedBorderColor:
-      return GetBorderColor("GtkEntry.entry");
+      return GetBorderColor("GtkEntry#entry");
 
     // Menu
     case ui::NativeTheme::kColorId_MenuBackgroundColor:
-      return GetBgColor("GtkMenu.menu");
+      return GetBgColor("GtkMenu#menu");
     case ui::NativeTheme::kColorId_MenuBorderColor:
-      return GetBorderColor("GtkMenu.menu");
+      return GetBorderColor("GtkMenu#menu");
     case ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor:
-      return GetBgColor("GtkMenu.menu GtkMenuItem.menuitem:focus");
+      return GetBgColor("GtkMenu#menu GtkMenuItem#menuitem:focus");
     case ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor:
-      return GetFgColor("GtkMenu.menu GtkMenuItem.menuitem GtkLabel.label");
+      return GetFgColor("GtkMenu#menu GtkMenuItem#menuitem GtkLabel#label");
     case ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor:
       return GetFgColor(
-          "GtkMenu.menu GtkMenuItem.menuitem:selected GtkLabel.label");
+          "GtkMenu#menu GtkMenuItem#menuitem:selected GtkLabel#label");
     case ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor:
       return GetFgColor(
-          "GtkMenu.menu GtkMenuItem.menuitem:disabled GtkLabel.label");
+          "GtkMenu#menu GtkMenuItem#menuitem:disabled GtkLabel#label");
     case ui::NativeTheme::kColorId_MenuItemSubtitleColor:
       return GetFgColor(
-          "GtkMenu.menu GtkMenuItem.menuitem GtkLabel.label.accelerator");
+          "GtkMenu#menu GtkMenuItem#menuitem GtkLabel#label.accelerator");
     case ui::NativeTheme::kColorId_MenuSeparatorColor:
     // MenuButton borders are used the same way as menu separators in Chrome.
     case ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor:
     case ui::NativeTheme::kColorId_FocusedMenuButtonBorderColor:
     case ui::NativeTheme::kColorId_HoverMenuButtonBorderColor:
-      return GetFgColor("GtkMenu.menu GtkMenuItem.menuitem.separator:disabled");
+      return GetFgColor("GtkMenu#menu GtkMenuItem#menuitem.separator:disabled");
 
     // Label
     case ui::NativeTheme::kColorId_LabelEnabledColor:
-      return GetFgColor("GtkLabel.label");
+      return GetFgColor("GtkLabel#label");
     case ui::NativeTheme::kColorId_LabelDisabledColor:
-      return GetFgColor("GtkLabel.label:disabled");
+      return GetFgColor("GtkLabel#label:disabled");
     case ui::NativeTheme::kColorId_LabelTextSelectionColor:
-      return GetFgColor("GtkLabel.label .selection:selected");
+      return GetFgColor("GtkLabel#label #selection:selected");
     case ui::NativeTheme::kColorId_LabelTextSelectionBackgroundFocused:
-      return GetBgColor("GtkLabel.label .selection:selected");
+      return GetBgColor("GtkLabel#label #selection:selected");
 
     // Link
     case ui::NativeTheme::kColorId_LinkDisabled:
@@ -121,7 +121,7 @@
     case ui::NativeTheme::kColorId_LinkEnabled: {
       // TODO(thomasanderson): Gtk changed the way links are colored in 3.12.
       // Add code for later versions.
-      auto link_context = GetStyleContextFromCss("GtkLabel.label.view");
+      auto link_context = GetStyleContextFromCss("GtkLabel#label.view");
       GdkColor* color;
       gtk_style_context_get_style(link_context, "link-color", &color, nullptr);
       if (color) {
@@ -137,143 +137,143 @@
 
     // Button
     case ui::NativeTheme::kColorId_ButtonEnabledColor:
-      return GetFgColor("GtkButton.button.text-button GtkLabel.label");
+      return GetFgColor("GtkButton#button.text-button GtkLabel#label");
     case ui::NativeTheme::kColorId_ButtonDisabledColor:
-      return GetFgColor("GtkButton.button.text-button:disabled GtkLabel.label");
+      return GetFgColor("GtkButton#button.text-button:disabled GtkLabel#label");
     case ui::NativeTheme::kColorId_ButtonHoverColor:
-      return GetFgColor("GtkButton.button.text-button:hover GtkLabel.label");
+      return GetFgColor("GtkButton#button.text-button:hover GtkLabel#label");
     case ui::NativeTheme::kColorId_ButtonPressedShade:
       return SK_ColorTRANSPARENT;
 
     case ui::NativeTheme::kColorId_BlueButtonEnabledColor:
       return GetFgColor(
-          "GtkButton.button.text-button.suggested-action GtkLabel.label");
+          "GtkButton#button.text-button.suggested-action GtkLabel#label");
     case ui::NativeTheme::kColorId_BlueButtonDisabledColor:
       return GetFgColor(
-          "GtkButton.button.text-button.suggested-action:disabled "
-          "GtkLabel.label");
+          "GtkButton#button.text-button.suggested-action:disabled "
+          "GtkLabel#label");
     case ui::NativeTheme::kColorId_BlueButtonHoverColor:
       return GetFgColor(
-          "GtkButton.button.text-button.suggested-action:hover GtkLabel.label");
+          "GtkButton#button.text-button.suggested-action:hover GtkLabel#label");
     case ui::NativeTheme::kColorId_BlueButtonPressedColor:
       return GetFgColor(
-          "GtkButton.button.text-button.suggested-action:hover:active "
-          "GtkLabel.label");
+          "GtkButton#button.text-button.suggested-action:hover:active "
+          "GtkLabel#label");
     case ui::NativeTheme::kColorId_BlueButtonShadowColor:
       return SK_ColorTRANSPARENT;
 
     case ui::NativeTheme::kColorId_ProminentButtonColor:
-      return GetBgColor("GtkButton.button.text-button.destructive-action");
+      return GetBgColor("GtkButton#button.text-button.destructive-action");
     case ui::NativeTheme::kColorId_TextOnProminentButtonColor:
       return GetFgColor(
-          "GtkButton.button.text-button.destructive-action GtkLabel.label");
+          "GtkButton#button.text-button.destructive-action GtkLabel#label");
 
     // Textfield
     case ui::NativeTheme::kColorId_TextfieldDefaultColor:
-      return GetFgColor("GtkEntry.entry");
+      return GetFgColor("GtkEntry#entry");
     case ui::NativeTheme::kColorId_TextfieldDefaultBackground:
-      return GetBgColor("GtkEntry.entry");
+      return GetBgColor("GtkEntry#entry");
     case ui::NativeTheme::kColorId_TextfieldReadOnlyColor:
-      return GetFgColor("GtkEntry.entry:disabled");
+      return GetFgColor("GtkEntry#entry:disabled");
     case ui::NativeTheme::kColorId_TextfieldReadOnlyBackground:
-      return GetBgColor("GtkEntry.entry:disabled");
+      return GetBgColor("GtkEntry#entry:disabled");
     case ui::NativeTheme::kColorId_TextfieldSelectionColor:
-      return GetFgColor("GtkEntry.entry .selection:selected");
+      return GetFgColor("GtkEntry#entry #selection:selected");
     case ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused:
-      return GetBgColor("GtkEntry.entry .selection:selected");
+      return GetBgColor("GtkEntry#entry #selection:selected");
 
     // Tooltips
     case ui::NativeTheme::kColorId_TooltipBackground:
-      return GetBgColor("GtkTooltip.tooltip");
+      return GetBgColor("GtkTooltip#tooltip");
     case ui::NativeTheme::kColorId_TooltipText:
-      return color_utils::GetReadableColor(GetFgColor("GtkTooltip.tooltip"),
-                                           GetBgColor("GtkTooltip.tooltip"));
+      return color_utils::GetReadableColor(GetFgColor("GtkTooltip#tooltip"),
+                                           GetBgColor("GtkTooltip#tooltip"));
 
     // Trees and Tables (implemented on GTK using the same class)
     case ui::NativeTheme::kColorId_TableBackground:
     case ui::NativeTheme::kColorId_TreeBackground:
-      return GetBgColor("GtkTreeView.view");
+      return GetBgColor("GtkTreeView#treeview.view");
     case ui::NativeTheme::kColorId_TableText:
     case ui::NativeTheme::kColorId_TreeText:
     case ui::NativeTheme::kColorId_TreeArrow:
     case ui::NativeTheme::kColorId_TableGroupingIndicatorColor:
-      return GetFgColor("GtkTreeView.view .cell");
+      return GetFgColor("GtkTreeView#treeview.view .cell");
     case ui::NativeTheme::kColorId_TableSelectedText:
     case ui::NativeTheme::kColorId_TableSelectedTextUnfocused:
     case ui::NativeTheme::kColorId_TreeSelectedText:
     case ui::NativeTheme::kColorId_TreeSelectedTextUnfocused:
-      return GetFgColor("GtkTreeView.view .cell:selected");
+      return GetFgColor("GtkTreeView#treeview.view .cell:selected");
     case ui::NativeTheme::kColorId_TableSelectionBackgroundFocused:
     case ui::NativeTheme::kColorId_TableSelectionBackgroundUnfocused:
     case ui::NativeTheme::kColorId_TreeSelectionBackgroundFocused:
     case ui::NativeTheme::kColorId_TreeSelectionBackgroundUnfocused:
-      return GetBgColor("GtkTreeView.view .cell:selected");
+      return GetBgColor("GtkTreeView#treeview.view .cell:selected");
 
     // Results Table
     // TODO(thomasanderson): The GtkEntry selectors was how the gtk2 theme got
     // these colors.  Update this code to use a different widget.
     case ui::NativeTheme::kColorId_ResultsTableNormalBackground:
-      return GetBgColor("GtkEntry.entry");
+      return GetBgColor("GtkEntry#entry");
     case ui::NativeTheme::kColorId_ResultsTableHoveredBackground:
       return color_utils::AlphaBlend(
-          GetBgColor("GtkEntry.entry"),
-          GetBgColor("GtkEntry.entry .selection:selected"), 0x80);
+          GetBgColor("GtkEntry#entry"),
+          GetBgColor("GtkEntry#entry #selection:selected"), 0x80);
     case ui::NativeTheme::kColorId_ResultsTableSelectedBackground:
-      return GetBgColor("GtkEntry.entry .selection:selected");
+      return GetBgColor("GtkEntry#entry #selection:selected");
     case ui::NativeTheme::kColorId_ResultsTableNormalText:
     case ui::NativeTheme::kColorId_ResultsTableHoveredText:
-      return GetFgColor("GtkEntry.entry");
+      return GetFgColor("GtkEntry#entry");
     case ui::NativeTheme::kColorId_ResultsTableSelectedText:
-      return GetFgColor("GtkEntry.entry .selection:selected");
+      return GetFgColor("GtkEntry#entry #selection:selected");
     case ui::NativeTheme::kColorId_ResultsTableNormalDimmedText:
     case ui::NativeTheme::kColorId_ResultsTableHoveredDimmedText:
-      return color_utils::AlphaBlend(GetFgColor("GtkEntry.entry"),
-                                     GetBgColor("GtkEntry.entry"), 0x80);
+      return color_utils::AlphaBlend(GetFgColor("GtkEntry#entry"),
+                                     GetBgColor("GtkEntry#entry"), 0x80);
     case ui::NativeTheme::kColorId_ResultsTableSelectedDimmedText:
       return color_utils::AlphaBlend(
-          GetFgColor("GtkEntry.entry .selection:selected"),
-          GetBgColor("GtkEntry.entry"), 0x80);
+          GetFgColor("GtkEntry#entry #selection:selected"),
+          GetBgColor("GtkEntry#entry"), 0x80);
     case ui::NativeTheme::kColorId_ResultsTableNormalUrl:
     case ui::NativeTheme::kColorId_ResultsTableHoveredUrl:
-      return NormalURLColor(GetFgColor("GtkEntry.entry"));
+      return NormalURLColor(GetFgColor("GtkEntry#entry"));
     case ui::NativeTheme::kColorId_ResultsTableSelectedUrl:
-      return SelectedURLColor(GetFgColor("GtkEntry.entry .selection:selected"),
-                              GetBgColor("GtkEntry.entry .selection:selected"));
+      return SelectedURLColor(GetFgColor("GtkEntry#entry #selection:selected"),
+                              GetBgColor("GtkEntry#entry #selection:selected"));
 
     case ui::NativeTheme::kColorId_ResultsTablePositiveText:
       return color_utils::GetReadableColor(kPositiveTextColor,
-                                           GetBgColor("GtkEntry.entry"));
+                                           GetBgColor("GtkEntry#entry"));
     case ui::NativeTheme::kColorId_ResultsTablePositiveHoveredText:
       return color_utils::GetReadableColor(kPositiveTextColor,
-                                           GetBgColor("GtkEntry.entry:hover"));
+                                           GetBgColor("GtkEntry#entry:hover"));
     case ui::NativeTheme::kColorId_ResultsTablePositiveSelectedText:
       return color_utils::GetReadableColor(
-          kPositiveTextColor, GetBgColor("GtkEntry.entry:selected"));
+          kPositiveTextColor, GetBgColor("GtkEntry#entry:selected"));
     case ui::NativeTheme::kColorId_ResultsTableNegativeText:
       return color_utils::GetReadableColor(kNegativeTextColor,
-                                           GetBgColor("GtkEntry.entry"));
+                                           GetBgColor("GtkEntry#entry"));
     case ui::NativeTheme::kColorId_ResultsTableNegativeHoveredText:
       return color_utils::GetReadableColor(kNegativeTextColor,
-                                           GetBgColor("GtkEntry.entry:hover"));
+                                           GetBgColor("GtkEntry#entry:hover"));
     case ui::NativeTheme::kColorId_ResultsTableNegativeSelectedText:
       return color_utils::GetReadableColor(
-          kNegativeTextColor, GetBgColor("GtkEntry.entry:selected"));
+          kNegativeTextColor, GetBgColor("GtkEntry#entry:selected"));
 
     // Throbber
     // TODO(thomasanderson): Render GtkSpinner directly.
     case ui::NativeTheme::kColorId_ThrobberSpinningColor:
     case ui::NativeTheme::kColorId_ThrobberWaitingColor:
-      return GetFgColor("GtkMenu.menu GtkSpinner.spinner");
+      return GetFgColor("GtkMenu#menu GtkSpinner#spinner");
     case ui::NativeTheme::kColorId_ThrobberLightColor:
-      return GetFgColor("GtkMenu.menu GtkSpinner.spinner:disabled");
+      return GetFgColor("GtkMenu#menu GtkSpinner#spinner:disabled");
 
     // Alert icons
     case ui::NativeTheme::kColorId_AlertSeverityLow:
-      return GetBgColor("GtkInfoBar.infobar.info");
+      return GetBgColor("GtkInfoBar#infobar.info");
     case ui::NativeTheme::kColorId_AlertSeverityMedium:
-      return GetBgColor("GtkInfoBar.infobar.warning");
+      return GetBgColor("GtkInfoBar#infobar.warning");
     case ui::NativeTheme::kColorId_AlertSeverityHigh:
-      return GetBgColor("GtkInfoBar.infobar.error");
+      return GetBgColor("GtkInfoBar#infobar.error");
 
     case ui::NativeTheme::kColorId_NumColors:
       NOTREACHED();
@@ -338,7 +338,7 @@
     SkCanvas* canvas,
     const gfx::Size& size,
     const MenuBackgroundExtraParams& menu_background) const {
-  PaintWidget(canvas, gfx::Rect(size), "GtkMenu.menu", GTK_STATE_FLAG_NORMAL);
+  PaintWidget(canvas, gfx::Rect(size), "GtkMenu#menu", GTK_STATE_FLAG_NORMAL);
 }
 
 void NativeThemeGtk3::PaintMenuItemBackground(
@@ -346,7 +346,7 @@
     State state,
     const gfx::Rect& rect,
     const MenuItemExtraParams& menu_item) const {
-  PaintWidget(canvas, rect, "GtkMenu.menu GtkMenuItem.menuitem",
+  PaintWidget(canvas, rect, "GtkMenu#menu GtkMenuItem#menuitem",
               StateToStateFlags(state));
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 5c599dd5..489d9b1 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1590,6 +1590,15 @@
 
 base::string16 BrowserView::GetAccessibleWindowTitle() const {
   const bool include_app_name = false;
+  int active_index = browser_->tab_strip_model()->active_index();
+  if (active_index > -1) {
+    if (IsIncognito()) {
+      return l10n_util::GetStringFUTF16(
+          IDS_ACCESSIBLE_INCOGNITO_WINDOW_TITLE_FORMAT,
+          GetAccessibleTabLabel(include_app_name, active_index));
+    }
+    return GetAccessibleTabLabel(include_app_name, active_index);
+  }
   if (IsIncognito()) {
     return l10n_util::GetStringFUTF16(
         IDS_ACCESSIBLE_INCOGNITO_WINDOW_TITLE_FORMAT,
@@ -1598,6 +1607,53 @@
   return browser_->GetWindowTitleForCurrentTab(include_app_name);
 }
 
+base::string16 BrowserView::GetAccessibleTabLabel(bool include_app_name,
+                                                  int index) const {
+  // ChromeVox provides an invalid index on browser start up before
+  // any tabs are created.
+  if (index == -1)
+    return base::string16();
+
+  base::string16 window_title =
+      browser_->GetWindowTitleForTab(include_app_name, index);
+  const TabRendererData& data = tabstrip_->tab_at(index)->data();
+
+  // Tab has crashed.
+  if (data.IsCrashed()) {
+    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_CRASHED_FORMAT,
+                                      window_title);
+  }
+  // Network error interstitial.
+  if (data.network_state == TabRendererData::NETWORK_STATE_ERROR) {
+    return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_NETWORK_ERROR_FORMAT,
+                                      window_title);
+  }
+  // Alert tab states.
+  switch (data.alert_state) {
+    case TabAlertState::AUDIO_PLAYING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT,
+                                        window_title);
+    case TabAlertState::USB_CONNECTED:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT,
+                                        window_title);
+    case TabAlertState::BLUETOOTH_CONNECTED:
+      return l10n_util::GetStringFUTF16(
+          IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT, window_title);
+    case TabAlertState::MEDIA_RECORDING:
+      return l10n_util::GetStringFUTF16(
+          IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT, window_title);
+    case TabAlertState::AUDIO_MUTING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT,
+                                        window_title);
+    case TabAlertState::TAB_CAPTURING:
+      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT,
+                                        window_title);
+    case TabAlertState::NONE:
+      return window_title;
+  }
+  return base::string16();
+}
+
 views::View* BrowserView::GetInitiallyFocusedView() {
   return nullptr;
 }
@@ -2018,8 +2074,7 @@
 
   // TabStrip takes ownership of the controller.
   BrowserTabStripController* tabstrip_controller =
-      new BrowserTabStripController(browser_.get(),
-                                    browser_->tab_strip_model());
+      new BrowserTabStripController(browser_->tab_strip_model(), this);
   tabstrip_ = new TabStrip(tabstrip_controller);
   top_container_->AddChildView(tabstrip_);
   tabstrip_controller->InitFromModel(tabstrip_);
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index d9e2f429..bcfdd06 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -32,6 +32,8 @@
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/frame/web_contents_close_handler.h"
 #include "chrome/browser/ui/views/load_complete_listener.h"
+#include "chrome/browser/ui/views/tabs/tab.h"
+#include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
 #include "chrome/common/features.h"
 #include "components/omnibox/browser/omnibox_popup_model_observer.h"
 #include "ui/base/accelerators/accelerator.h"
@@ -466,6 +468,11 @@
   extensions::ActiveTabPermissionGranter* GetActiveTabPermissionGranter()
       override;
 
+  // Creates an accessible tab label for screen readers that includes the tab
+  // status for the given tab index. This takes the form of
+  // "Page title - Tab state".
+  base::string16 GetAccessibleTabLabel(bool include_app_name, int index) const;
+
   // Testing interface:
   views::View* GetContentsContainerForTest() { return contents_container_; }
   views::WebView* GetContentsWebViewForTest() { return contents_web_view_; }
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 21c34beb..59d15a7 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -34,6 +33,9 @@
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/browser/user_metrics.h"
@@ -54,8 +56,17 @@
 
 TabRendererData::NetworkState TabContentsNetworkState(
     WebContents* contents) {
-  if (!contents || !contents->IsLoadingToDifferentDocument())
+  if (!contents)
     return TabRendererData::NETWORK_STATE_NONE;
+
+  if (!contents->IsLoadingToDifferentDocument()) {
+    content::NavigationEntry* entry =
+        contents->GetController().GetLastCommittedEntry();
+    if (entry && (entry->GetPageType() == content::PAGE_TYPE_ERROR))
+      return TabRendererData::NETWORK_STATE_ERROR;
+    return TabRendererData::NETWORK_STATE_NONE;
+  }
+
   if (contents->IsWaitingForResponse())
     return TabRendererData::NETWORK_STATE_WAITING;
   return TabRendererData::NETWORK_STATE_LOADING;
@@ -179,11 +190,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 // BrowserTabStripController, public:
 
-BrowserTabStripController::BrowserTabStripController(Browser* browser,
-                                                     TabStripModel* model)
+BrowserTabStripController::BrowserTabStripController(TabStripModel* model,
+                                                     BrowserView* browser_view)
     : model_(model),
       tabstrip_(NULL),
-      browser_(browser),
+      browser_view_(browser_view),
       hover_tab_selector_(model),
       weak_ptr_factory_(this) {
   model_->AddObserver(this);
@@ -337,7 +348,8 @@
 void BrowserTabStripController::PerformDrop(bool drop_before,
                                             int index,
                                             const GURL& url) {
-  chrome::NavigateParams params(browser_, url, ui::PAGE_TRANSITION_LINK);
+  chrome::NavigateParams params(browser_view_->browser(), url,
+                                ui::PAGE_TRANSITION_LINK);
   params.tabstrip_index = index;
 
   if (drop_before) {
@@ -374,7 +386,8 @@
 }
 
 bool BrowserTabStripController::IsIncognito() {
-  return browser_->profile()->GetProfileType() == Profile::INCOGNITO_PROFILE;
+  return browser_view_->browser()->profile()->GetProfileType() ==
+      Profile::INCOGNITO_PROFILE;
 }
 
 void BrowserTabStripController::StackedLayoutMaybeChanged() {
@@ -389,14 +402,13 @@
 }
 
 void BrowserTabStripController::OnStartedDraggingTabs() {
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
-  if (browser_view && !immersive_reveal_lock_.get()) {
+  if (!immersive_reveal_lock_.get()) {
     // The top-of-window views should be revealed while the user is dragging
     // tabs in immersive fullscreen. The top-of-window views may not be already
     // revealed if the user is attempting to attach a tab to a tabstrip
     // belonging to an immersive fullscreen window.
     immersive_reveal_lock_.reset(
-        browser_view->immersive_mode_controller()->GetRevealedLock(
+        browser_view_->immersive_mode_controller()->GetRevealedLock(
             ImmersiveModeController::ANIMATE_REVEAL_NO));
   }
 }
@@ -416,8 +428,13 @@
 }
 
 SkColor BrowserTabStripController::GetToolbarTopSeparatorColor() const {
-  return BrowserView::GetBrowserViewForBrowser(browser_)->frame()
-      ->GetFrameView()->GetToolbarTopSeparatorColor();
+  return browser_view_->frame()->GetFrameView()->GetToolbarTopSeparatorColor();
+}
+
+base::string16 BrowserTabStripController::GetAccessibleTabName(
+    const Tab* tab) const {
+  return browser_view_->GetAccessibleTabLabel(
+      false /* include_app_name */, tabstrip_->GetModelIndexOfTab(tab));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index 63f6bff..bde789a 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "chrome/browser/ui/tabs/hover_tab_selector.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -33,7 +34,7 @@
 class BrowserTabStripController : public TabStripController,
                                   public TabStripModelObserver {
  public:
-  BrowserTabStripController(Browser* browser, TabStripModel* model);
+  BrowserTabStripController(TabStripModel* model, BrowserView* browser_view);
   ~BrowserTabStripController() override;
 
   void InitFromModel(TabStrip* tabstrip);
@@ -76,6 +77,7 @@
   void OnStoppedDraggingTabs() override;
   void CheckFileSupported(const GURL& url) override;
   SkColor GetToolbarTopSeparatorColor() const override;
+  base::string16 GetAccessibleTabName(const Tab* tab) const override;
 
   // TabStripModelObserver implementation:
   void TabInsertedAt(TabStripModel* tab_strip_model,
@@ -101,6 +103,8 @@
   void TabBlockedStateChanged(content::WebContents* contents,
                               int model_index) override;
 
+  const Browser* browser() const { return browser_view_->browser(); }
+
  protected:
   // The context in which SetTabRendererDataFromModel is being called.
   enum TabStatus {
@@ -118,8 +122,6 @@
 
   const TabStrip* tabstrip() const { return tabstrip_; }
 
-  const Browser* browser() const { return browser_; }
-
  private:
   class TabContextMenuContents;
 
@@ -148,8 +150,7 @@
 
   TabStrip* tabstrip_;
 
-  // Non-owning pointer to the browser which is using this controller.
-  Browser* browser_;
+  BrowserView* browser_view_;
 
   // If non-NULL it means we're showing a menu for the tab.
   std::unique_ptr<TabContextMenuContents> context_menu_contents_;
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
index 44a03a3..d4c0337 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
@@ -148,3 +148,8 @@
 SkColor FakeBaseTabStripController::GetToolbarTopSeparatorColor() const {
   return SK_ColorBLACK;
 }
+
+base::string16 FakeBaseTabStripController::GetAccessibleTabName(
+    const Tab* tab) const {
+  return base::string16();
+}
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
index e2ef1142..28318dfa 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
@@ -55,6 +55,7 @@
   void OnStoppedDraggingTabs() override;
   void CheckFileSupported(const GURL& url) override;
   SkColor GetToolbarTopSeparatorColor() const override;
+  base::string16 GetAccessibleTabName(const Tab* tab) const override;
 
  private:
   TabStrip* tab_strip_;
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index 5a080a6..d88bfdfe 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -19,8 +19,11 @@
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/view_ids.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/alert_indicator_button.h"
+#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_controller.h"
+#include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
@@ -494,7 +497,8 @@
 
 void Tab::ThrobberView::OnPaint(gfx::Canvas* canvas) {
   const TabRendererData::NetworkState state = owner_->data().network_state;
-  if (state == TabRendererData::NETWORK_STATE_NONE)
+  if (state == TabRendererData::NETWORK_STATE_NONE ||
+      state == TabRendererData::NETWORK_STATE_ERROR)
     return;
 
   const ui::ThemeProvider* tp = GetThemeProvider();
@@ -674,9 +678,10 @@
 
 void Tab::UpdateLoadingAnimation(TabRendererData::NetworkState state) {
   if (state == data_.network_state &&
-      state == TabRendererData::NETWORK_STATE_NONE) {
-    // If the network state is none and hasn't changed, do nothing. Otherwise we
-    // need to advance the animation frame.
+      (state == TabRendererData::NETWORK_STATE_NONE ||
+       state == TabRendererData::NETWORK_STATE_ERROR)) {
+    // If the network state is none or is a network error and hasn't changed,
+    // do nothing. Otherwise we need to advance the animation frame.
     return;
   }
 
@@ -1099,7 +1104,7 @@
 
 void Tab::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ui::AX_ROLE_TAB;
-  node_data->SetName(data_.title);
+  node_data->SetName(controller_->GetAccessibleTabName(this));
   node_data->AddStateFlag(ui::AX_STATE_MULTISELECTABLE);
   node_data->AddStateFlag(ui::AX_STATE_SELECTABLE);
   controller_->UpdateTabAccessibilityState(this, node_data);
@@ -1184,7 +1189,8 @@
   // Paint network activity indicator.
   // TODO(jamescook): Replace this placeholder animation with a real one.
   // For now, let's go with a Cylon eye effect, but in blue.
-  if (data().network_state != TabRendererData::NETWORK_STATE_NONE) {
+  if (data().network_state != TabRendererData::NETWORK_STATE_NONE &&
+      data().network_state != TabRendererData::NETWORK_STATE_ERROR) {
     const SkColor kEyeColor = SkColorSetARGB(alpha, 71, 138, 217);
     int eye_width = bar_rect.width() / 3;
     int eye_offset = bar_rect.width() * immersive_loading_step_ /
@@ -1389,9 +1395,10 @@
     return;
 
   // Throbber will do its own painting.
-  if (data().network_state != TabRendererData::NETWORK_STATE_NONE)
+  if (data().network_state != TabRendererData::NETWORK_STATE_NONE &&
+      data().network_state != TabRendererData::NETWORK_STATE_ERROR) {
     return;
-
+  }
   // Ensure that |favicon_| is created.
   if (favicon_.isNull()) {
     ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
@@ -1441,7 +1448,8 @@
     return;
   }
 
-  if (state == TabRendererData::NETWORK_STATE_NONE) {
+  if (state == TabRendererData::NETWORK_STATE_NONE ||
+      state == TabRendererData::NETWORK_STATE_ERROR) {
     throbber_->ResetStartTimes();
     throbber_->SetVisible(false);
     ScheduleIconPaint();
diff --git a/chrome/browser/ui/views/tabs/tab_controller.h b/chrome/browser/ui/views/tabs/tab_controller.h
index a28d5e5..872c169 100644
--- a/chrome/browser/ui/views/tabs/tab_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_controller.h
@@ -129,6 +129,9 @@
   virtual void UpdateTabAccessibilityState(const Tab* tab,
                                            ui::AXNodeData* node_data) = 0;
 
+  // Returns the accessible tab name for this tab.
+  virtual base::string16 GetAccessibleTabName(const Tab* tab) const = 0;
+
  protected:
   virtual ~TabController() {}
 };
diff --git a/chrome/browser/ui/views/tabs/tab_renderer_data.h b/chrome/browser/ui/views/tabs/tab_renderer_data.h
index 829de7e..9b1d2ab 100644
--- a/chrome/browser/ui/views/tabs/tab_renderer_data.h
+++ b/chrome/browser/ui/views/tabs/tab_renderer_data.h
@@ -20,6 +20,7 @@
     NETWORK_STATE_NONE,     // no network activity.
     NETWORK_STATE_WAITING,  // waiting for a connection.
     NETWORK_STATE_LOADING,  // connected, transferring data.
+    NETWORK_STATE_ERROR,    // Encountered a network error.
   };
 
   TabRendererData();
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 1e6c8ef..7f721df 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1324,6 +1324,14 @@
   return controller_->GetToolbarTopSeparatorColor();
 }
 
+// Returns the accessible tab name for the tab.
+base::string16 TabStrip::GetAccessibleTabName(const Tab* tab) const {
+  int model_index = GetModelIndexOfTab(tab);
+  if (IsValidModelIndex(model_index))
+    return controller_->GetAccessibleTabName(tab);
+  return base::string16();
+}
+
 int TabStrip::GetBackgroundResourceId(bool* custom_image) const {
   const ui::ThemeProvider* tp = GetThemeProvider();
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 6022c86..4512b4d 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -239,6 +239,7 @@
   bool CanPaintThrobberToLayer() const override;
   bool IsImmersiveStyle() const override;
   SkColor GetToolbarTopSeparatorColor() const override;
+  base::string16 GetAccessibleTabName(const Tab* tab) const override;
   int GetBackgroundResourceId(bool* custom_image) const override;
   void UpdateTabAccessibilityState(const Tab* tab,
                                    ui::AXNodeData* node_data) override;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_controller.h b/chrome/browser/ui/views/tabs/tab_strip_controller.h
index 8e80d365..f06590b 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_controller.h
@@ -123,6 +123,9 @@
   // Returns COLOR_TOOLBAR_TOP_SEPARATOR[,_INACTIVE] depending on the activation
   // state of the window.
   virtual SkColor GetToolbarTopSeparatorColor() const = 0;
+
+  // Returns the accessible tab name.
+  virtual base::string16 GetAccessibleTabName(const Tab* tab) const = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/tabs/tab_unittest.cc b/chrome/browser/ui/views/tabs/tab_unittest.cc
index e78ebde..528b802 100644
--- a/chrome/browser/ui/views/tabs/tab_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_unittest.cc
@@ -81,6 +81,9 @@
   }
   void UpdateTabAccessibilityState(const Tab* tab,
                                    ui::AXNodeData* node_data) override{};
+  base::string16 GetAccessibleTabName(const Tab* tab) const override {
+    return base::string16();
+  }
 
  private:
   ui::ListSelectionModel selection_model_;
diff --git a/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc b/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc
index a324f0a..0f41cca 100644
--- a/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc
+++ b/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc
@@ -717,10 +717,6 @@
   SizeToContents();
 }
 
-void WebsiteSettingsPopupView::SetSelectedTab(TabId tab_id) {
-  // TODO(lgarron): Remove this method. (https://crbug.com/571533)
-}
-
 views::View* WebsiteSettingsPopupView::CreateSiteSettingsView() {
   views::View* site_settings_view = new views::View();
   views::BoxLayout* box_layout =
diff --git a/chrome/browser/ui/views/website_settings/website_settings_popup_view.h b/chrome/browser/ui/views/website_settings/website_settings_popup_view.h
index 7bd9379..ea6da6a 100644
--- a/chrome/browser/ui/views/website_settings/website_settings_popup_view.h
+++ b/chrome/browser/ui/views/website_settings/website_settings_popup_view.h
@@ -129,8 +129,6 @@
   void SetPermissionInfo(const PermissionInfoList& permission_info_list,
                          ChosenObjectInfoList chosen_object_info_list) override;
   void SetIdentityInfo(const IdentityInfo& identity_info) override;
-  // TODO(lgarron): Remove SetSelectedTab() with https://crbug.com/571533
-  void SetSelectedTab(TabId tab_id) override;
 
   // Creates the contents of the |site_settings_view_|. The ownership of the
   // returned view is transferred to the caller.
diff --git a/chrome/browser/ui/website_settings/website_settings.cc b/chrome/browser/ui/website_settings/website_settings.cc
index c9dae32d..9a7f925 100644
--- a/chrome/browser/ui/website_settings/website_settings.cc
+++ b/chrome/browser/ui/website_settings/website_settings.cc
@@ -666,32 +666,6 @@
   // Only show an SSL decision revoke button if the user has chosen to bypass
   // SSL host errors for this host in the past.
   show_ssl_decision_revoke_button_ = delegate->HasAllowException(url.host());
-
-  // By default select the Permissions Tab that displays all the site
-  // permissions. In case of a connection error or an issue with the certificate
-  // presented by the website, select the Connection Tab to draw the user's
-  // attention to the issue. If the site does not provide a certificate because
-  // it was loaded over an unencrypted connection, don't select the Connection
-  // Tab.
-  WebsiteSettingsUI::TabId tab_id = WebsiteSettingsUI::TAB_ID_PERMISSIONS;
-  if (site_connection_status_ == SITE_CONNECTION_STATUS_ENCRYPTED_ERROR ||
-      site_connection_status_ ==
-          SITE_CONNECTION_STATUS_INSECURE_PASSIVE_SUBRESOURCE ||
-      site_connection_status_ ==
-          SITE_CONNECTION_STATUS_INSECURE_ACTIVE_SUBRESOURCE ||
-      site_identity_status_ == SITE_IDENTITY_STATUS_ERROR ||
-      site_identity_status_ == SITE_IDENTITY_STATUS_CERT_REVOCATION_UNKNOWN ||
-      site_identity_status_ == SITE_IDENTITY_STATUS_ADMIN_PROVIDED_CERT ||
-      site_identity_status_ ==
-          SITE_IDENTITY_STATUS_DEPRECATED_SIGNATURE_ALGORITHM_MINOR ||
-      site_identity_status_ ==
-          SITE_IDENTITY_STATUS_DEPRECATED_SIGNATURE_ALGORITHM_MAJOR) {
-    tab_id = WebsiteSettingsUI::TAB_ID_CONNECTION;
-    RecordWebsiteSettingsAction(
-      WEBSITE_SETTINGS_CONNECTION_TAB_SHOWN_IMMEDIATELY);
-  }
-
-  ui_->SetSelectedTab(tab_id);
 }
 
 void WebsiteSettings::PresentSitePermissions() {
diff --git a/chrome/browser/ui/website_settings/website_settings.h b/chrome/browser/ui/website_settings/website_settings.h
index 7b9b4aca..bac9695 100644
--- a/chrome/browser/ui/website_settings/website_settings.h
+++ b/chrome/browser/ui/website_settings/website_settings.h
@@ -89,9 +89,11 @@
   // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.pageinfo
   enum WebsiteSettingsAction {
     WEBSITE_SETTINGS_OPENED = 0,
-    WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED = 1,
-    WEBSITE_SETTINGS_CONNECTION_TAB_SELECTED = 2,
-    WEBSITE_SETTINGS_CONNECTION_TAB_SHOWN_IMMEDIATELY = 3,
+    // No longer used; indicated actions for the old version of Page Info that
+    // had a "Permissions" tab and a "Connection" tab.
+    // WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED = 1,
+    // WEBSITE_SETTINGS_CONNECTION_TAB_SELECTED = 2,
+    // WEBSITE_SETTINGS_CONNECTION_TAB_SHOWN_IMMEDIATELY = 3,
     WEBSITE_SETTINGS_COOKIES_DIALOG_OPENED = 4,
     WEBSITE_SETTINGS_CHANGED_PERMISSION = 5,
     WEBSITE_SETTINGS_CERTIFICATE_DIALOG_OPENED = 6,
diff --git a/chrome/browser/ui/website_settings/website_settings_ui.h b/chrome/browser/ui/website_settings/website_settings_ui.h
index 23d11404..a656ae57 100644
--- a/chrome/browser/ui/website_settings/website_settings_ui.h
+++ b/chrome/browser/ui/website_settings/website_settings_ui.h
@@ -196,9 +196,6 @@
 
   // Sets site identity information.
   virtual void SetIdentityInfo(const IdentityInfo& identity_info) = 0;
-
-  // Selects the tab with the given |tab_id|.
-  virtual void SetSelectedTab(TabId tab_id) = 0;
 };
 
 typedef WebsiteSettingsUI::CookieInfoList CookieInfoList;
diff --git a/chrome/browser/ui/website_settings/website_settings_unittest.cc b/chrome/browser/ui/website_settings/website_settings_unittest.cc
index 14631ee8..024e5ae6 100644
--- a/chrome/browser/ui/website_settings/website_settings_unittest.cc
+++ b/chrome/browser/ui/website_settings/website_settings_unittest.cc
@@ -74,7 +74,6 @@
   MOCK_METHOD1(SetCookieInfo, void(const CookieInfoList& cookie_info_list));
   MOCK_METHOD0(SetPermissionInfoStub, void());
   MOCK_METHOD1(SetIdentityInfo, void(const IdentityInfo& identity_info));
-  MOCK_METHOD1(SetSelectedTab, void(TabId tab_id));
 
   void SetPermissionInfo(
       const PermissionInfoList& permission_info_list,
@@ -226,8 +225,6 @@
 #else
   EXPECT_CALL(*mock_ui(), SetPermissionInfoStub()).Times(7);
 #endif
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   // Execute code under tests.
   website_settings()->OnSitePermissionChanged(CONTENT_SETTINGS_TYPE_POPUPS,
@@ -272,8 +269,6 @@
   EXPECT_CALL(*mock_ui(), SetPermissionInfoStub());
   EXPECT_CALL(*mock_ui(), SetIdentityInfo(_));
   EXPECT_CALL(*mock_ui(), SetCookieInfo(_)).Times(2);
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   website_settings()->OnSiteDataAccessed();
 }
@@ -287,8 +282,6 @@
 
   EXPECT_CALL(*mock_ui(), SetIdentityInfo(_));
   EXPECT_CALL(*mock_ui(), SetCookieInfo(_));
-  EXPECT_CALL(*mock_ui(),
-              SetSelectedTab(WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   // Access WebsiteSettings so that SetPermissionInfo is called once to populate
   // |last_chosen_object_info_|. It will be called again by
@@ -310,8 +303,6 @@
   security_info_.malicious_content_status =
       security_state::MALICIOUS_CONTENT_STATUS_MALWARE;
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(),
-              SetSelectedTab(WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_UNENCRYPTED,
             website_settings()->site_connection_status());
@@ -324,8 +315,6 @@
   security_info_.malicious_content_status =
       security_state::MALICIOUS_CONTENT_STATUS_SOCIAL_ENGINEERING;
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(),
-              SetSelectedTab(WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_UNENCRYPTED,
             website_settings()->site_connection_status());
@@ -338,8 +327,6 @@
   security_info_.malicious_content_status =
       security_state::MALICIOUS_CONTENT_STATUS_UNWANTED_SOFTWARE;
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(),
-              SetSelectedTab(WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_UNENCRYPTED,
             website_settings()->site_connection_status());
@@ -349,8 +336,6 @@
 
 TEST_F(WebsiteSettingsTest, HTTPConnection) {
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_UNENCRYPTED,
             website_settings()->site_connection_status());
   EXPECT_EQ(WebsiteSettings::SITE_IDENTITY_STATUS_NO_CERT,
@@ -370,8 +355,6 @@
   security_info_.connection_status = status;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED,
             website_settings()->site_connection_status());
@@ -508,8 +491,6 @@
     security_info_.connection_status = status;
 
     SetDefaultUIExpectations(mock_ui());
-    EXPECT_CALL(*mock_ui(),
-                SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
     EXPECT_EQ(test.expected_site_connection_status,
               website_settings()->site_connection_status());
@@ -541,7 +522,6 @@
   security_info_.connection_status = status;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(
       WebsiteSettings::SITE_CONNECTION_STATUS_INSECURE_PASSIVE_SUBRESOURCE,
@@ -564,7 +544,6 @@
   security_info_.connection_status = status;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED,
             website_settings()->site_connection_status());
@@ -585,7 +564,6 @@
   security_info_.connection_status = status;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED_ERROR,
             website_settings()->site_connection_status());
@@ -607,7 +585,6 @@
   security_info_.connection_status = status;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED,
             website_settings()->site_connection_status());
@@ -630,7 +607,6 @@
       security_state::DEPRECATED_SHA1_MINOR;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED,
             website_settings()->site_connection_status());
@@ -657,7 +633,6 @@
       security_state::DEPRECATED_SHA1_MAJOR;
 
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION));
 
   EXPECT_EQ(WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED,
             website_settings()->site_connection_status());
@@ -673,8 +648,6 @@
 #if !defined(OS_ANDROID)
 TEST_F(WebsiteSettingsTest, NoInfoBar) {
   SetDefaultUIExpectations(mock_ui());
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
   EXPECT_EQ(0u, infobar_service()->infobar_count());
   website_settings()->OnUIClosing();
   EXPECT_EQ(0u, infobar_service()->infobar_count());
@@ -686,8 +659,6 @@
 
   EXPECT_CALL(*mock_ui(), SetPermissionInfoStub()).Times(2);
 
-  EXPECT_CALL(*mock_ui(), SetSelectedTab(
-      WebsiteSettingsUI::TAB_ID_PERMISSIONS));
   EXPECT_EQ(0u, infobar_service()->infobar_count());
   website_settings()->OnSitePermissionChanged(
       CONTENT_SETTINGS_TYPE_GEOLOCATION, CONTENT_SETTING_ALLOW);
@@ -762,7 +733,7 @@
 
     website_settings()->RecordWebsiteSettingsAction(
         WebsiteSettings::WebsiteSettingsAction::
-            WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED);
+            WEBSITE_SETTINGS_OPENED);
 
     // RecordWebsiteSettingsAction() is called during WebsiteSettings
     // creation in addition to the explicit RecordWebsiteSettingsAction()
@@ -770,19 +741,11 @@
     histograms.ExpectTotalCount(kGenericHistogram, 2);
     histograms.ExpectBucketCount(
         kGenericHistogram,
-        WebsiteSettings::WebsiteSettingsAction::WEBSITE_SETTINGS_OPENED, 1);
-    histograms.ExpectBucketCount(kGenericHistogram,
-                                 WebsiteSettings::WebsiteSettingsAction::
-                                     WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED,
-                                 1);
+        WebsiteSettings::WebsiteSettingsAction::WEBSITE_SETTINGS_OPENED, 2);
 
     histograms.ExpectTotalCount(test.histogram_name, 2);
     histograms.ExpectBucketCount(
         test.histogram_name,
-        WebsiteSettings::WebsiteSettingsAction::WEBSITE_SETTINGS_OPENED, 1);
-    histograms.ExpectBucketCount(test.histogram_name,
-                                 WebsiteSettings::WebsiteSettingsAction::
-                                     WEBSITE_SETTINGS_PERMISSIONS_TAB_SELECTED,
-                                 1);
+        WebsiteSettings::WebsiteSettingsAction::WEBSITE_SETTINGS_OPENED, 2);
   }
 }
diff --git a/chrome/common/extensions/docs/templates/articles/desktop_notifications.html b/chrome/common/extensions/docs/templates/articles/desktop_notifications.html
index ba325b0..31654ff 100644
--- a/chrome/common/extensions/docs/templates/articles/desktop_notifications.html
+++ b/chrome/common/extensions/docs/templates/articles/desktop_notifications.html
@@ -48,7 +48,7 @@
     "notifications"
   ]</b>,
   ...
-  // <strong>Note:</strong> Because of <a href="http://code.google.com/p/chromium/issues/detail?id=134315">bug 134315</a>, you must declare any images you
+  // <strong>Note:</strong> Because of <a href="http://bugs.chromium.org/p/chromium/issues/detail?id=134315">bug 134315</a>, you must declare any images you
   // want to use with createNotification() as a web accessible resource.
 <b>  "web_accessible_resources": [
     "48.png"
diff --git a/chrome/common/extensions/docs/templates/articles/faq.html b/chrome/common/extensions/docs/templates/articles/faq.html
index 7ebda44a9..95ead99f 100644
--- a/chrome/common/extensions/docs/templates/articles/faq.html
+++ b/chrome/common/extensions/docs/templates/articles/faq.html
@@ -6,8 +6,8 @@
 <p>
 If you don't find an answer to your question here,
 try the
-<a href="https://developer.chrome.com/webstore/faq">Chrome Web Store FAQ</a>,
-the {{?is_apps}}
+<a href="https://developer.chrome.com/webstore/faq">Chrome Web Store FAQ</a>, the
+{{?is_apps}}
 <a href="http://stackoverflow.com/questions/tagged/google-chrome-app">[google-chrome-app] tag on Stack Overflow</a>, the
 <a href="http://groups.google.com/a/chromium.org/group/chromium-apps">chromium-apps group</a>, or the
 {{:is_apps}}
@@ -404,12 +404,12 @@
     Search the issue tracker at
     <a href="http://crbug.com">http://crbug.com</a> to see whether
     someone has reported a similar issue.  Most issues related to
-    extensions are filed under <strong>Cr=Platform-Extensions</strong>, so to
+    extensions are filed under <strong>component=Platform>Extensions</strong>, so to
     look for an extension bug related to the
     chrome.tabs.executeScript function (for example), search for
-    "<code>Cr=Platform-Extensions Type=Bug chrome.tabs.executeScript</code>",
+    "<code>component=Platform>Extensions Type=Bug chrome.tabs.executeScript</code>",
     which will give you
-    <a href="https://code.google.com/p/chromium/issues/list?can=2&q=Cr%3DPlatform-Extensions+Type%3DBug+chrome.tabs.executeScript&colspec=ID+Pri+M+Iteration+ReleaseBlock+Cr+Status+Owner+Summary+Modified&x=m&y=releaseblock&cells=tiles">
+    <a href="https://bugs.chromium.org/p/chromium/issues/list?can=2&q=component%3DPlatform>Extensions+Type%3DBug+chrome.tabs.executeScript">
     this list of results</a>.
   </li>
   <li>
@@ -459,12 +459,12 @@
     Search the issue tracker at
     <a href="http://crbug.com">http://crbug.com</a> to see whether
     someone has requested a similar feature.  Most requests related to
-    extensions are filed under <strong>Cr=Platform-Extensions</strong>, so to
+    extensions are filed under <strong>component=Platform>Extensions</strong>, so to
     look for an extension feature request related to keyboard shortcuts
     (for example), search
-    for "<code>Cr=Platform-Extensions Type=Feature shortcuts</code>",
+    for "<code>component=Platform>Extensions Type=Feature shortcuts</code>",
     which will give you
-    <a href="https://code.google.com/p/chromium/issues/list?can=2&q=Cr%3DPlatform-Extensions+Type%3DFeature+shortcuts&colspec=ID+Pri+M+Iteration+ReleaseBlock+Cr+Status+Owner+Summary+Modified&x=m&y=releaseblock&cells=tiles">
+    <a href="https://bugs.chromium.org/p/chromium/issues/list?can=2&q=component%3DPlatform>Extensions+Type%3DFeature+shortcuts">
     this list of results</a>.
   </li>
   <li>
diff --git a/chrome/test/data/android/payments/email_and_phone.js b/chrome/test/data/android/payments/email_and_phone.js
new file mode 100644
index 0000000..bc76e72
--- /dev/null
+++ b/chrome/test/data/android/payments/email_and_phone.js
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+/* global PaymentRequest:false */
+
+/*
+ * Launches the PaymentRequest UI that requests email address and phone number.
+ */
+function buy() {  // eslint-disable-line no-unused-vars
+  try {
+    new PaymentRequest(
+        [{supportedMethods: ['https://bobpay.com']}],
+        {total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}},
+        {requestPayerEmail: true, requestPayerPhone: true})
+        .show()
+        .then(function(resp) {
+          resp.complete('success')
+              .then(function() {
+                print(JSON.stringify(resp, undefined, 2));
+              })
+              .catch(function(error) {
+                print(error);
+              });
+        })
+        .catch(function(error) {
+          print(error);
+        });
+  } catch (error) {
+    print(error.message);
+  }
+}
diff --git a/chrome/test/data/android/payments/payment_request_email_and_phone_test.html b/chrome/test/data/android/payments/payment_request_email_and_phone_test.html
new file mode 100644
index 0000000..2c65337
--- /dev/null
+++ b/chrome/test/data/android/payments/payment_request_email_and_phone_test.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<head>
+<title>Email and Phone Test</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+<body>
+<button onclick="buy()" id="buy">Email and Phone Test</button>
+<pre id="result"></pre>
+<script src="util.js"></script>
+<script src="email_and_phone.js"></script>
+</body>
+</html>
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index b0b18ab..26bcf8f 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -54,6 +54,8 @@
     "cast_media_default.cc",
     "media_pipeline_backend_default.cc",
     "media_pipeline_backend_default.h",
+    "media_sink_default.cc",
+    "media_sink_default.h",
     "video_decoder_default.cc",
     "video_decoder_default.h",
   ]
@@ -62,6 +64,7 @@
     "//base",
     "//chromecast/base",
     "//chromecast/public/media",
+    "//media",
   ]
 }
 
diff --git a/chromecast/media/cma/backend/audio_decoder_default.cc b/chromecast/media/cma/backend/audio_decoder_default.cc
index 58504df2..84388d7 100644
--- a/chromecast/media/cma/backend/audio_decoder_default.cc
+++ b/chromecast/media/cma/backend/audio_decoder_default.cc
@@ -4,42 +4,45 @@
 
 #include "chromecast/media/cma/backend/audio_decoder_default.h"
 
-#include <limits>
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "chromecast/public/media/cast_decoder_buffer.h"
+#include "base/memory/ptr_util.h"
+#include "chromecast/media/cma/backend/media_sink_default.h"
 
 namespace chromecast {
 namespace media {
 
-AudioDecoderDefault::AudioDecoderDefault()
-    : delegate_(nullptr),
-      last_push_pts_(std::numeric_limits<int64_t>::min()),
-      weak_factory_(this) {}
+AudioDecoderDefault::AudioDecoderDefault() : delegate_(nullptr) {}
 
 AudioDecoderDefault::~AudioDecoderDefault() {}
 
+void AudioDecoderDefault::Start(base::TimeDelta start_pts) {
+  DCHECK(!sink_);
+  sink_ = base::MakeUnique<MediaSinkDefault>(delegate_, start_pts);
+}
+
+void AudioDecoderDefault::Stop() {
+  DCHECK(sink_);
+  sink_.reset();
+}
+
+void AudioDecoderDefault::SetPlaybackRate(float rate) {
+  DCHECK(sink_);
+  sink_->SetPlaybackRate(rate);
+}
+
+base::TimeDelta AudioDecoderDefault::GetCurrentPts() {
+  DCHECK(sink_);
+  return sink_->GetCurrentPts();
+}
+
 void AudioDecoderDefault::SetDelegate(Delegate* delegate) {
-  DCHECK(delegate);
+  DCHECK(!sink_);
   delegate_ = delegate;
 }
 
 MediaPipelineBackend::BufferStatus AudioDecoderDefault::PushBuffer(
     CastDecoderBuffer* buffer) {
-  DCHECK(delegate_);
-  DCHECK(buffer);
-
-  if (buffer->end_of_stream()) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(&AudioDecoderDefault::OnEndOfStream,
-                              weak_factory_.GetWeakPtr()));
-  } else {
-    last_push_pts_ = buffer->timestamp();
-  }
-  return MediaPipelineBackend::kBufferSuccess;
+  DCHECK(sink_);
+  return sink_->PushBuffer(buffer);
 }
 
 void AudioDecoderDefault::GetStatistics(Statistics* statistics) {
@@ -57,9 +60,5 @@
   return RenderingDelay();
 }
 
-void AudioDecoderDefault::OnEndOfStream() {
-  delegate_->OnEndOfStream();
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/audio_decoder_default.h b/chromecast/media/cma/backend/audio_decoder_default.h
index ea712dad..142de3b9 100644
--- a/chromecast/media/cma/backend/audio_decoder_default.h
+++ b/chromecast/media/cma/backend/audio_decoder_default.h
@@ -5,21 +5,26 @@
 #ifndef CHROMECAST_MEDIA_CMA_BACKEND_AUDIO_DECODER_DEFAULT_H_
 #define CHROMECAST_MEDIA_CMA_BACKEND_AUDIO_DECODER_DEFAULT_H_
 
-#include <stdint.h>
+#include <memory>
 
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 
 namespace chromecast {
 namespace media {
 
+class MediaSinkDefault;
+
 class AudioDecoderDefault : public MediaPipelineBackend::AudioDecoder {
  public:
   AudioDecoderDefault();
   ~AudioDecoderDefault() override;
 
-  int64_t last_push_pts() const { return last_push_pts_; }
+  void Start(base::TimeDelta start_pts);
+  void Stop();
+  void SetPlaybackRate(float rate);
+  base::TimeDelta GetCurrentPts();
 
   // MediaPipelineBackend::AudioDecoder implementation:
   void SetDelegate(Delegate* delegate) override;
@@ -31,12 +36,8 @@
   RenderingDelay GetRenderingDelay() override;
 
  private:
-  void OnEndOfStream();
-
   Delegate* delegate_;
-  int64_t last_push_pts_;
-  base::WeakPtrFactory<AudioDecoderDefault> weak_factory_;
-
+  std::unique_ptr<MediaSinkDefault> sink_;
   DISALLOW_COPY_AND_ASSIGN(AudioDecoderDefault);
 };
 
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_default.cc b/chromecast/media/cma/backend/media_pipeline_backend_default.cc
index f47e2cd..b41cd4c8 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_default.cc
+++ b/chromecast/media/cma/backend/media_pipeline_backend_default.cc
@@ -4,95 +4,115 @@
 
 #include "chromecast/media/cma/backend/media_pipeline_backend_default.h"
 
-#include <algorithm>
-#include <limits>
-
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
 #include "chromecast/media/cma/backend/audio_decoder_default.h"
 #include "chromecast/media/cma/backend/video_decoder_default.h"
-#include "chromecast/public/media/cast_decoder_buffer.h"
+#include "media/base/timestamp_constants.h"
 
 namespace chromecast {
 namespace media {
 
 MediaPipelineBackendDefault::MediaPipelineBackendDefault()
-    : start_pts_(std::numeric_limits<int64_t>::min()),
-      running_(false),
-      rate_(1.0f) {}
+    : state_(kStateUninitialized), rate_(1.0f) {}
 
 MediaPipelineBackendDefault::~MediaPipelineBackendDefault() {
 }
 
 MediaPipelineBackend::AudioDecoder*
 MediaPipelineBackendDefault::CreateAudioDecoder() {
+  DCHECK_EQ(kStateUninitialized, state_);
   DCHECK(!audio_decoder_);
-  audio_decoder_.reset(new AudioDecoderDefault());
+  audio_decoder_ = base::MakeUnique<AudioDecoderDefault>();
   return audio_decoder_.get();
 }
 
 MediaPipelineBackend::VideoDecoder*
 MediaPipelineBackendDefault::CreateVideoDecoder() {
+  DCHECK_EQ(kStateUninitialized, state_);
   DCHECK(!video_decoder_);
-  video_decoder_.reset(new VideoDecoderDefault());
+  video_decoder_ = base::MakeUnique<VideoDecoderDefault>();
   return video_decoder_.get();
 }
 
 bool MediaPipelineBackendDefault::Initialize() {
+  DCHECK_EQ(kStateUninitialized, state_);
+  state_ = kStateInitialized;
   return true;
 }
 
 bool MediaPipelineBackendDefault::Start(int64_t start_pts) {
-  DCHECK(!running_);
-  start_pts_ = start_pts;
-  start_clock_ = base::TimeTicks::Now();
-  running_ = true;
+  DCHECK_EQ(kStateInitialized, state_);
+  if (!audio_decoder_ && !video_decoder_)
+    return false;
+
+  if (audio_decoder_) {
+    audio_decoder_->Start(base::TimeDelta::FromMicroseconds(start_pts));
+    audio_decoder_->SetPlaybackRate(rate_);
+  }
+  if (video_decoder_) {
+    video_decoder_->Start(base::TimeDelta::FromMicroseconds(start_pts));
+    video_decoder_->SetPlaybackRate(rate_);
+  }
+  state_ = kStatePlaying;
   return true;
 }
 
 void MediaPipelineBackendDefault::Stop() {
-  start_pts_ = GetCurrentPts();
-  running_ = false;
+  DCHECK(state_ == kStatePlaying || state_ == kStatePaused);
+  if (audio_decoder_)
+    audio_decoder_->Stop();
+  if (video_decoder_)
+    video_decoder_->Stop();
+  state_ = kStateInitialized;
 }
 
 bool MediaPipelineBackendDefault::Pause() {
-  DCHECK(running_);
-  start_pts_ = GetCurrentPts();
-  running_ = false;
+  DCHECK_EQ(kStatePlaying, state_);
+  if (audio_decoder_)
+    audio_decoder_->SetPlaybackRate(0.0f);
+  if (video_decoder_)
+    video_decoder_->SetPlaybackRate(0.0f);
+  state_ = kStatePaused;
   return true;
 }
 
 bool MediaPipelineBackendDefault::Resume() {
-  DCHECK(!running_);
-  running_ = true;
-  start_clock_ = base::TimeTicks::Now();
+  DCHECK_EQ(kStatePaused, state_);
+  if (audio_decoder_)
+    audio_decoder_->SetPlaybackRate(rate_);
+  if (video_decoder_)
+    video_decoder_->SetPlaybackRate(rate_);
+  state_ = kStatePlaying;
   return true;
 }
 
 int64_t MediaPipelineBackendDefault::GetCurrentPts() {
-  if (!running_)
-    return start_pts_;
+  base::TimeDelta current_pts = ::media::kNoTimestamp;
 
-  if (audio_decoder_ &&
-      audio_decoder_->last_push_pts() != std::numeric_limits<int64_t>::min()) {
-    start_pts_ = std::min(start_pts_, audio_decoder_->last_push_pts());
-  }
-  if (video_decoder_ &&
-      video_decoder_->last_push_pts() != std::numeric_limits<int64_t>::min()) {
-    start_pts_ = std::min(start_pts_, video_decoder_->last_push_pts());
+  if (audio_decoder_ && video_decoder_) {
+    current_pts = std::min(audio_decoder_->GetCurrentPts(),
+                           video_decoder_->GetCurrentPts());
+  } else if (audio_decoder_) {
+    current_pts = audio_decoder_->GetCurrentPts();
+  } else if (video_decoder_) {
+    current_pts = video_decoder_->GetCurrentPts();
   }
 
-  base::TimeTicks now = base::TimeTicks::Now();
-  base::TimeDelta interpolated_media_time =
-      base::TimeDelta::FromMicroseconds(start_pts_) +
-      (now - start_clock_) * rate_;
-
-  return interpolated_media_time.InMicroseconds();
+  return current_pts.InMicroseconds();
 }
 
 bool MediaPipelineBackendDefault::SetPlaybackRate(float rate) {
   DCHECK_GT(rate, 0.0f);
-  start_pts_ = GetCurrentPts();
-  start_clock_ = base::TimeTicks::Now();
   rate_ = rate;
+
+  if (state_ == kStatePlaying) {
+    if (audio_decoder_)
+      audio_decoder_->SetPlaybackRate(rate_);
+    if (video_decoder_)
+      video_decoder_->SetPlaybackRate(rate_);
+  }
+
   return true;
 }
 
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_default.h b/chromecast/media/cma/backend/media_pipeline_backend_default.h
index c838797..b1a44287 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_default.h
+++ b/chromecast/media/cma/backend/media_pipeline_backend_default.h
@@ -10,7 +10,6 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/time/time.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 
 namespace chromecast {
@@ -24,7 +23,6 @@
   MediaPipelineBackendDefault();
   ~MediaPipelineBackendDefault() override;
 
-  bool running() const { return running_; }
   const AudioDecoderDefault* audio_decoder() const {
     return audio_decoder_.get();
   }
@@ -44,11 +42,14 @@
   bool SetPlaybackRate(float rate) override;
 
  private:
-  int64_t start_pts_;
-  base::TimeTicks start_clock_;
-  bool running_;
+  enum State {
+    kStateUninitialized,
+    kStateInitialized,
+    kStatePlaying,
+    kStatePaused,
+  };
+  State state_;
   float rate_;
-
   std::unique_ptr<AudioDecoderDefault> audio_decoder_;
   std::unique_ptr<VideoDecoderDefault> video_decoder_;
 
diff --git a/chromecast/media/cma/backend/media_sink_default.cc b/chromecast/media/cma/backend/media_sink_default.cc
new file mode 100644
index 0000000..61d58c6
--- /dev/null
+++ b/chromecast/media/cma/backend/media_sink_default.cc
@@ -0,0 +1,89 @@
+// 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 "chromecast/media/cma/backend/media_sink_default.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chromecast/public/media/cast_decoder_buffer.h"
+#include "media/base/timestamp_constants.h"
+
+namespace chromecast {
+namespace media {
+
+MediaSinkDefault::MediaSinkDefault(
+    MediaPipelineBackend::Decoder::Delegate* delegate,
+    base::TimeDelta start_pts)
+    : delegate_(delegate),
+      time_interpolator_(&tick_clock_),
+      playback_rate_(1.0f),
+      last_frame_pts_(start_pts),
+      received_eos_(false) {
+  DCHECK(delegate_);
+  time_interpolator_.SetPlaybackRate(playback_rate_);
+  time_interpolator_.SetBounds(start_pts, start_pts, tick_clock_.NowTicks());
+  time_interpolator_.StartInterpolating();
+}
+
+MediaSinkDefault::~MediaSinkDefault() {}
+
+void MediaSinkDefault::SetPlaybackRate(float rate) {
+  DCHECK_GE(rate, 0.0f);
+  playback_rate_ = rate;
+  time_interpolator_.SetPlaybackRate(playback_rate_);
+
+  // Changing the playback rate affects the delay after which EOS callback
+  // should run. Reschedule the task according to the new delay.
+  if (received_eos_) {
+    eos_task_.Cancel();
+    ScheduleEndOfStreamTask();
+  }
+}
+
+base::TimeDelta MediaSinkDefault::GetCurrentPts() {
+  return time_interpolator_.GetInterpolatedTime();
+}
+
+MediaPipelineBackend::BufferStatus MediaSinkDefault::PushBuffer(
+    CastDecoderBuffer* buffer) {
+  if (buffer->end_of_stream()) {
+    received_eos_ = true;
+    ScheduleEndOfStreamTask();
+    return MediaPipelineBackend::kBufferSuccess;
+  }
+
+  // This is wrong on several levels.
+  // 1. The correct PTS should be buffer->timestamp() + buffer->duration().
+  //    But CastDecoderBuffer does not expose duration unlike
+  //    ::media::DecoderBuffer.
+  // 2. The PTS reported by GetCurrentPts should not move backwards.
+  //    It should be clamped in the range [start_pts, last_frame_pts_].
+  //    But doing so makes several AudioVideoPipelineDeviceTest cases fail.
+  //    Those tests are wrong should be fixed.
+  // TODO(alokp): Fix these issues when the next version of CMA backend is
+  // scheduled to roll out. crbug.com/678394
+  last_frame_pts_ = base::TimeDelta::FromMicroseconds(buffer->timestamp());
+  time_interpolator_.SetUpperBound(last_frame_pts_);
+  return MediaPipelineBackend::kBufferSuccess;
+}
+
+void MediaSinkDefault::ScheduleEndOfStreamTask() {
+  DCHECK(received_eos_);
+  DCHECK(eos_task_.IsCancelled());
+
+  // Do not schedule if playback is paused.
+  if (playback_rate_ == 0.0f)
+    return;
+
+  eos_task_.Reset(
+      base::Bind(&MediaPipelineBackend::Decoder::Delegate::OnEndOfStream,
+                 base::Unretained(delegate_)));
+  base::TimeDelta delay = (last_frame_pts_ - GetCurrentPts()) / playback_rate_;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, eos_task_.callback(), delay);
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/cma/backend/media_sink_default.h b/chromecast/media/cma/backend/media_sink_default.h
new file mode 100644
index 0000000..89f7c7b
--- /dev/null
+++ b/chromecast/media/cma/backend/media_sink_default.h
@@ -0,0 +1,44 @@
+// 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 CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_SINK_DEFAULT_H_
+#define CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_SINK_DEFAULT_H_
+
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/time/default_tick_clock.h"
+#include "chromecast/public/media/media_pipeline_backend.h"
+#include "media/base/time_delta_interpolator.h"
+
+namespace chromecast {
+namespace media {
+
+class MediaSinkDefault {
+ public:
+  MediaSinkDefault(MediaPipelineBackend::Decoder::Delegate* delegate,
+                   base::TimeDelta start_pts);
+  ~MediaSinkDefault();
+
+  void SetPlaybackRate(float rate);
+  base::TimeDelta GetCurrentPts();
+  MediaPipelineBackend::BufferStatus PushBuffer(CastDecoderBuffer* buffer);
+
+ private:
+  void ScheduleEndOfStreamTask();
+
+  MediaPipelineBackend::Decoder::Delegate* delegate_;
+  base::DefaultTickClock tick_clock_;
+  ::media::TimeDeltaInterpolator time_interpolator_;
+  float playback_rate_;
+  base::TimeDelta last_frame_pts_;
+  bool received_eos_;
+  base::CancelableClosure eos_task_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaSinkDefault);
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_CMA_BACKEND_MEDIA_SINK_DEFAULT_H_
diff --git a/chromecast/media/cma/backend/video_decoder_default.cc b/chromecast/media/cma/backend/video_decoder_default.cc
index 552c7b5..60ee8a5b 100644
--- a/chromecast/media/cma/backend/video_decoder_default.cc
+++ b/chromecast/media/cma/backend/video_decoder_default.cc
@@ -4,41 +4,45 @@
 
 #include "chromecast/media/cma/backend/video_decoder_default.h"
 
-#include <limits>
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "chromecast/public/media/cast_decoder_buffer.h"
+#include "base/memory/ptr_util.h"
+#include "chromecast/media/cma/backend/media_sink_default.h"
 
 namespace chromecast {
 namespace media {
 
-VideoDecoderDefault::VideoDecoderDefault()
-    : delegate_(nullptr),
-      last_push_pts_(std::numeric_limits<int64_t>::min()),
-      weak_factory_(this) {}
+VideoDecoderDefault::VideoDecoderDefault() {}
 
 VideoDecoderDefault::~VideoDecoderDefault() {}
 
+void VideoDecoderDefault::Start(base::TimeDelta start_pts) {
+  DCHECK(!sink_);
+  sink_ = base::MakeUnique<MediaSinkDefault>(delegate_, start_pts);
+}
+
+void VideoDecoderDefault::Stop() {
+  DCHECK(sink_);
+  sink_.reset();
+}
+
+void VideoDecoderDefault::SetPlaybackRate(float rate) {
+  DCHECK(sink_);
+  sink_->SetPlaybackRate(rate);
+}
+
+base::TimeDelta VideoDecoderDefault::GetCurrentPts() {
+  DCHECK(sink_);
+  return sink_->GetCurrentPts();
+}
+
 void VideoDecoderDefault::SetDelegate(Delegate* delegate) {
-  DCHECK(delegate);
+  DCHECK(!sink_);
   delegate_ = delegate;
 }
 
 MediaPipelineBackend::BufferStatus VideoDecoderDefault::PushBuffer(
     CastDecoderBuffer* buffer) {
-  DCHECK(delegate_);
-  DCHECK(buffer);
-  if (buffer->end_of_stream()) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(&VideoDecoderDefault::OnEndOfStream,
-                              weak_factory_.GetWeakPtr()));
-  } else {
-    last_push_pts_ = buffer->timestamp();
-  }
-  return MediaPipelineBackend::kBufferSuccess;
+  DCHECK(sink_);
+  return sink_->PushBuffer(buffer);
 }
 
 void VideoDecoderDefault::GetStatistics(Statistics* statistics) {
@@ -48,9 +52,5 @@
   return true;
 }
 
-void VideoDecoderDefault::OnEndOfStream() {
-  delegate_->OnEndOfStream();
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/cma/backend/video_decoder_default.h b/chromecast/media/cma/backend/video_decoder_default.h
index b56a485..d91b1a2 100644
--- a/chromecast/media/cma/backend/video_decoder_default.h
+++ b/chromecast/media/cma/backend/video_decoder_default.h
@@ -5,21 +5,26 @@
 #ifndef CHROMECAST_MEDIA_CMA_BACKEND_VIDEO_DECODER_DEFAULT_H_
 #define CHROMECAST_MEDIA_CMA_BACKEND_VIDEO_DECODER_DEFAULT_H_
 
-#include <stdint.h>
+#include <memory>
 
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
 
 namespace chromecast {
 namespace media {
 
+class MediaSinkDefault;
+
 class VideoDecoderDefault : public MediaPipelineBackend::VideoDecoder {
  public:
   VideoDecoderDefault();
   ~VideoDecoderDefault() override;
 
-  int64_t last_push_pts() const { return last_push_pts_; }
+  void Start(base::TimeDelta start_pts);
+  void Stop();
+  void SetPlaybackRate(float rate);
+  base::TimeDelta GetCurrentPts();
 
   // MediaPipelineBackend::VideoDecoder implementation:
   void SetDelegate(Delegate* delegate) override;
@@ -29,12 +34,8 @@
   bool SetConfig(const VideoConfig& config) override;
 
  private:
-  void OnEndOfStream();
-
   Delegate* delegate_;
-  int64_t last_push_pts_;
-  base::WeakPtrFactory<VideoDecoderDefault> weak_factory_;
-
+  std::unique_ptr<MediaSinkDefault> sink_;
   DISALLOW_COPY_AND_ASSIGN(VideoDecoderDefault);
 };
 
diff --git a/components/error_page/common/localized_error.cc b/components/error_page/common/localized_error.cc
index a420821..5192a11 100644
--- a/components/error_page/common/localized_error.cc
+++ b/components/error_page/common/localized_error.cc
@@ -1037,8 +1037,9 @@
   }
 
 #if defined(OS_ANDROID)
-  if (!reload_visible && !show_saved_copy_visible && !is_incognito &&
-      failed_url.is_valid() && failed_url.SchemeIsHTTPOrHTTPS() &&
+  if (!is_post && !reload_visible && !show_saved_copy_visible &&
+      !is_incognito && failed_url.is_valid() &&
+      failed_url.SchemeIsHTTPOrHTTPS() &&
       offline_pages::IsOfflinePagesAsyncDownloadEnabled()) {
     std::unique_ptr<base::DictionaryValue> download_button =
         base::MakeUnique<base::DictionaryValue>();
diff --git a/components/safe_browsing_db/v4_local_database_manager.cc b/components/safe_browsing_db/v4_local_database_manager.cc
index 3b798c5c..5bd7702 100644
--- a/components/safe_browsing_db/v4_local_database_manager.cc
+++ b/components/safe_browsing_db/v4_local_database_manager.cc
@@ -535,6 +535,7 @@
   }
 
   // Post on the IO thread to enforce async behavior.
+  pending_clients_.insert(check->client);
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
       base::Bind(&V4LocalDatabaseManager::PerformFullHashCheck, this,
@@ -603,8 +604,6 @@
   DCHECK(enabled_);
   DCHECK(!full_hash_to_store_and_hash_prefixes.empty());
 
-  pending_clients_.insert(check->client);
-
   v4_get_hash_protocol_manager_->GetFullHashes(
       full_hash_to_store_and_hash_prefixes,
       base::Bind(&V4LocalDatabaseManager::OnFullHashResponse,
@@ -613,23 +612,32 @@
 
 void V4LocalDatabaseManager::ProcessQueuedChecks() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  for (auto& it : queued_checks_) {
+
+  // Steal the queue to protect against reentrant CancelCheck() calls.
+  QueuedChecks checks;
+  checks.swap(queued_checks_);
+
+  for (auto& it : checks) {
     FullHashToStoreAndHashPrefixesMap full_hash_to_store_and_hash_prefixes;
     if (!GetPrefixMatches(it, &full_hash_to_store_and_hash_prefixes)) {
       RespondToClient(std::move(it));
     } else {
+      pending_clients_.insert(it->client);
       PerformFullHashCheck(std::move(it), full_hash_to_store_and_hash_prefixes);
     }
   }
-  queued_checks_.clear();
 }
 
 void V4LocalDatabaseManager::RespondSafeToQueuedChecks() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  for (std::unique_ptr<PendingCheck>& it : queued_checks_) {
+
+  // Steal the queue to protect against reentrant CancelCheck() calls.
+  QueuedChecks checks;
+  checks.swap(queued_checks_);
+
+  for (std::unique_ptr<PendingCheck>& it : checks) {
     RespondToClient(std::move(it));
   }
-  queued_checks_.clear();
 }
 
 void V4LocalDatabaseManager::RespondToClient(
diff --git a/components/safe_browsing_db/v4_local_database_manager_unittest.cc b/components/safe_browsing_db/v4_local_database_manager_unittest.cc
index 3b03db0e..3b1a68619 100644
--- a/components/safe_browsing_db/v4_local_database_manager_unittest.cc
+++ b/components/safe_browsing_db/v4_local_database_manager_unittest.cc
@@ -6,6 +6,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/test_simple_task_runner.h"
 #include "components/safe_browsing_db/v4_database.h"
 #include "components/safe_browsing_db/v4_local_database_manager.h"
@@ -27,6 +28,16 @@
   return full_hashes[0];
 }
 
+// A fullhash response containing no matches.
+std::string GetEmptyV4HashResponse() {
+  FindFullHashesResponse res;
+  res.mutable_negative_cache_duration()->set_seconds(600);
+
+  std::string res_data;
+  res.SerializeToString(&res_data);
+  return res_data;
+}
+
 }  // namespace
 
 class FakeV4Database : public V4Database {
@@ -96,18 +107,29 @@
 
 class TestClient : public SafeBrowsingDatabaseManager::Client {
  public:
-  TestClient(SBThreatType sb_threat_type, const GURL& url)
-      : expected_sb_threat_type(sb_threat_type), expected_url(url) {}
+  TestClient(SBThreatType sb_threat_type,
+             const GURL& url,
+             V4LocalDatabaseManager* manager_to_cancel = nullptr)
+      : expected_sb_threat_type(sb_threat_type),
+        expected_url(url),
+        result_received_(false),
+        manager_to_cancel_(manager_to_cancel) {}
 
   void OnCheckBrowseUrlResult(const GURL& url,
                               SBThreatType threat_type,
                               const ThreatMetadata& metadata) override {
     DCHECK_EQ(expected_url, url);
     DCHECK_EQ(expected_sb_threat_type, threat_type);
+    result_received_ = true;
+    if (manager_to_cancel_) {
+      manager_to_cancel_->CancelCheck(this);
+    }
   }
 
   SBThreatType expected_sb_threat_type;
   GURL expected_url;
+  bool result_received_;
+  V4LocalDatabaseManager* manager_to_cancel_;
 };
 
 class FakeV4LocalDatabaseManager : public V4LocalDatabaseManager {
@@ -348,6 +370,80 @@
   EXPECT_TRUE(GetQueuedChecks().empty());
 }
 
+// Verify that a window where checks cannot be cancelled is closed.
+TEST_F(V4LocalDatabaseManagerTest, CancelPending) {
+  WaitForTasksOnTaskRunner();
+  net::FakeURLFetcherFactory factory(NULL);
+  // TODO(shess): Modify this to use a mock protocol manager instead
+  // of faking the requests.
+  const char* kReqs[] = {
+      // OSX
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaJwgBCAIIAwgGCAcICAgJCAoQBBAIGgcKBWVXGg-"
+      "pIAEgAyAEIAUgBg==",
+
+      // Linux
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaJwgBCAIIAwgGCAcICAgJCAoQAhAIGgcKBWVXGg-"
+      "pIAEgAyAEIAUgBg==",
+
+      // Windows
+      "Cg8KCHVuaXR0ZXN0EgMxLjAaJwgBCAIIAwgGCAcICAgJCAoQARAIGgcKBWVXGg-"
+      "pIAEgAyAEIAUgBg==",
+  };
+  for (const char* req : kReqs) {
+    const GURL url(
+        base::StringPrintf("https://safebrowsing.googleapis.com/v4/"
+                           "fullHashes:find?$req=%s"
+                           "&$ct=application/x-protobuf&key=test_key_param",
+                           req));
+    factory.SetFakeResponse(url, GetEmptyV4HashResponse(), net::HTTP_OK,
+                            net::URLRequestStatus::SUCCESS);
+  }
+
+  const GURL url("http://example.com/a/");
+  const HashPrefix hash_prefix("eW\x1A\xF\xA9");
+
+  StoreAndHashPrefixes store_and_hash_prefixes;
+  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), hash_prefix);
+  ReplaceV4Database(store_and_hash_prefixes);
+
+  // Test that a request flows through to the callback.
+  {
+    TestClient client(SB_THREAT_TYPE_SAFE, url);
+    EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(url, &client));
+    EXPECT_FALSE(client.result_received_);
+    WaitForTasksOnTaskRunner();
+    EXPECT_TRUE(client.result_received_);
+  }
+
+  // Test that cancel prevents the callback from being called.
+  {
+    TestClient client(SB_THREAT_TYPE_SAFE, url);
+    EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(url, &client));
+    v4_local_database_manager_->CancelCheck(&client);
+    EXPECT_FALSE(client.result_received_);
+    WaitForTasksOnTaskRunner();
+    EXPECT_FALSE(client.result_received_);
+  }
+}
+
+// When the database load flushes the queued requests, make sure that
+// CancelCheck() is not fatal in the client callback.
+TEST_F(V4LocalDatabaseManagerTest, CancelQueued) {
+  const GURL url("http://example.com/a/");
+
+  TestClient client1(SB_THREAT_TYPE_SAFE, url,
+                     v4_local_database_manager_.get());
+  TestClient client2(SB_THREAT_TYPE_SAFE, url);
+  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(url, &client1));
+  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(url, &client2));
+  EXPECT_EQ(2ul, GetQueuedChecks().size());
+  EXPECT_FALSE(client1.result_received_);
+  EXPECT_FALSE(client2.result_received_);
+  WaitForTasksOnTaskRunner();
+  EXPECT_TRUE(client1.result_received_);
+  EXPECT_TRUE(client2.result_received_);
+}
+
 // This test is somewhat similar to TestCheckBrowseUrlWithFakeDbReturnsMatch but
 // it uses a fake V4LocalDatabaseManager to assert that PerformFullHashCheck is
 // called async.
diff --git a/content/renderer/presentation/presentation_dispatcher.h b/content/renderer/presentation/presentation_dispatcher.h
index 91dba49..6b6e1b3 100644
--- a/content/renderer/presentation/presentation_dispatcher.h
+++ b/content/renderer/presentation/presentation_dispatcher.h
@@ -36,6 +36,8 @@
 
 namespace content {
 
+class TestPresentationDispatcher;
+
 // PresentationDispatcher is a delegate for Presentation API messages used by
 // Blink. It forwards the calls to the Mojo PresentationService.
 class CONTENT_EXPORT PresentationDispatcher
@@ -47,6 +49,21 @@
   ~PresentationDispatcher() override;
 
  private:
+  friend class TestPresentationDispatcher;
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestStartSession);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestStartSessionError);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestJoinSession);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestJoinSessionError);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestSendString);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestSendArrayBuffer);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestSendBlobData);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestCloseSession);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest, TestTerminateSession);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest,
+                           TestListenForScreenAvailability);
+  FRIEND_TEST_ALL_PREFIXES(PresentationDispatcherTest,
+                           TestSetDefaultPresentationUrls);
+
   struct SendMessageRequest {
     SendMessageRequest(blink::mojom::PresentationSessionInfoPtr session_info,
                        blink::mojom::ConnectionMessagePtr message);
@@ -137,7 +154,7 @@
   void DoSendMessage(SendMessageRequest* request);
   void HandleSendMessageRequests(bool success);
 
-  void ConnectToPresentationServiceIfNeeded();
+  virtual void ConnectToPresentationServiceIfNeeded();
 
   void UpdateListeningState();
 
diff --git a/content/renderer/presentation/presentation_dispatcher_unittest.cc b/content/renderer/presentation/presentation_dispatcher_unittest.cc
new file mode 100644
index 0000000..9397f85
--- /dev/null
+++ b/content/renderer/presentation/presentation_dispatcher_unittest.cc
@@ -0,0 +1,409 @@
+// 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 <memory>
+#include <utility>
+
+#include "base/run_loop.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/renderer/presentation/presentation_dispatcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationAvailabilityObserver.h"
+#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
+#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationSessionInfo.h"
+#include "third_party/WebKit/public/web/WebArrayBuffer.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using blink::WebArrayBuffer;
+using blink::WebPresentationAvailabilityCallbacks;
+using blink::WebPresentationAvailabilityObserver;
+using blink::WebPresentationConnectionCallback;
+using blink::WebPresentationError;
+using blink::WebPresentationSessionInfo;
+using blink::WebString;
+using blink::WebURL;
+using blink::WebVector;
+using blink::mojom::PresentationError;
+using blink::mojom::PresentationErrorPtr;
+using blink::mojom::PresentationErrorType;
+using blink::mojom::PresentationService;
+using blink::mojom::PresentationServiceClientPtr;
+using blink::mojom::PresentationSessionInfo;
+using blink::mojom::PresentationSessionInfoPtr;
+using blink::mojom::ConnectionMessage;
+using blink::mojom::ConnectionMessagePtr;
+
+// TODO(crbug.com/576808): Add test cases for the following:
+// - State changes
+// - Messages received
+// - Discarding queued messages when the frame navigates
+// - Screen availability not supported
+// - Default presentation starting
+
+namespace content {
+
+class MockPresentationAvailabilityObserver
+    : public WebPresentationAvailabilityObserver {
+ public:
+  explicit MockPresentationAvailabilityObserver(WebURL url) : url_(url) {}
+  ~MockPresentationAvailabilityObserver() override {}
+
+  MOCK_METHOD1(availabilityChanged, void(bool is_available));
+  const WebURL url() const override { return url_; }
+
+ private:
+  const WebURL url_;
+};
+
+class MockPresentationService : public PresentationService {
+ public:
+  void SetClient(PresentationServiceClientPtr client) override {}
+  MOCK_METHOD1(SetDefaultPresentationUrls,
+               void(const std::vector<GURL>& presentation_urls));
+  MOCK_METHOD1(ListenForScreenAvailability, void(const GURL& availability_url));
+  MOCK_METHOD1(StopListeningForScreenAvailability,
+               void(const GURL& availability_url));
+  MOCK_METHOD2(StartSession,
+               void(const std::vector<GURL>& presentation_urls,
+                    const StartSessionCallback& callback));
+  MOCK_METHOD3(JoinSession,
+               void(const std::vector<GURL>& presentation_urls,
+                    const base::Optional<std::string>& presentation_id,
+                    const JoinSessionCallback& callback));
+
+  // *Internal method is to work around lack of support for move-only types in
+  // GMock.
+  void SendConnectionMessage(
+      PresentationSessionInfoPtr session_info,
+      ConnectionMessagePtr message_request,
+      const SendConnectionMessageCallback& callback) override {
+    SendConnectionMessageInternal(session_info.get(), message_request.get(),
+                                  callback);
+  }
+  MOCK_METHOD3(SendConnectionMessageInternal,
+               void(PresentationSessionInfo* session_info,
+                    ConnectionMessage* message_request,
+                    const SendConnectionMessageCallback& callback));
+
+  MOCK_METHOD2(CloseConnection,
+               void(const GURL& presentation_url,
+                    const std::string& presentation_id));
+  MOCK_METHOD2(Terminate,
+               void(const GURL& presentation_url,
+                    const std::string& presentation_id));
+
+  // *Internal method is to work around lack of support for move-only types in
+  // GMock.
+  void ListenForConnectionMessages(
+      PresentationSessionInfoPtr session_info) override {
+    ListenForConnectionMessagesInternal(session_info.get());
+  }
+  MOCK_METHOD1(ListenForConnectionMessagesInternal,
+               void(PresentationSessionInfo* session_info));
+};
+
+class TestWebPresentationConnectionCallback
+    : public WebPresentationConnectionCallback {
+ public:
+  TestWebPresentationConnectionCallback(WebURL url, WebString id)
+      : url_(url), id_(id), callback_called_(false) {}
+  ~TestWebPresentationConnectionCallback() override {
+    EXPECT_TRUE(callback_called_);
+  }
+
+  void onSuccess(const WebPresentationSessionInfo& info) override {
+    callback_called_ = true;
+    EXPECT_EQ(info.url, url_);
+    EXPECT_EQ(info.id, id_);
+  }
+
+ private:
+  const WebURL url_;
+  const WebString id_;
+  bool callback_called_;
+};
+
+class TestWebPresentationConnectionErrorCallback
+    : public WebPresentationConnectionCallback {
+ public:
+  TestWebPresentationConnectionErrorCallback(
+      WebPresentationError::ErrorType error_type,
+      WebString message)
+      : error_type_(error_type), message_(message), callback_called_(false) {}
+  ~TestWebPresentationConnectionErrorCallback() override {
+    EXPECT_TRUE(callback_called_);
+  }
+
+  void onError(const WebPresentationError& error) override {
+    callback_called_ = true;
+    EXPECT_EQ(error.errorType, error_type_);
+    EXPECT_EQ(error.message, message_);
+  }
+
+ private:
+  const WebPresentationError::ErrorType error_type_;
+  const WebString message_;
+  bool callback_called_;
+};
+
+class TestPresentationDispatcher : public PresentationDispatcher {
+ public:
+  explicit TestPresentationDispatcher(
+      MockPresentationService* presentation_service)
+      : PresentationDispatcher(nullptr),
+        mock_presentation_service_(presentation_service) {}
+  ~TestPresentationDispatcher() override {}
+
+ private:
+  void ConnectToPresentationServiceIfNeeded() override {
+    if (!mock_binding_) {
+      mock_binding_ = base::MakeUnique<mojo::Binding<PresentationService>>(
+          mock_presentation_service_,
+          mojo::MakeRequest(&presentation_service_));
+    }
+  }
+
+  MockPresentationService* mock_presentation_service_;
+  std::unique_ptr<mojo::Binding<PresentationService>> mock_binding_;
+};
+
+class PresentationDispatcherTest : public ::testing::Test {
+ public:
+  PresentationDispatcherTest()
+      : gurl1_(GURL("https://www.example.com/1.html")),
+        gurl2_(GURL("https://www.example.com/2.html")),
+        gurls_({gurl1_, gurl2_}),
+        url1_(WebURL(gurl1_)),
+        url2_(WebURL(gurl2_)),
+        urls_(WebVector<WebURL>(gurls_)),
+        presentation_id_(WebString::fromUTF8("test-id")),
+        array_buffer_(WebArrayBuffer::create(4, 1)),
+        observer_(url1_),
+        dispatcher_(&presentation_service_) {}
+  ~PresentationDispatcherTest() override {}
+
+  void SetUp() override {
+    // Set some test data.
+    *array_buffer_data() = 42;
+  }
+
+  uint8_t* array_buffer_data() {
+    return static_cast<uint8_t*>(array_buffer_.data());
+  }
+
+ protected:
+  const GURL gurl1_;
+  const GURL gurl2_;
+  const std::vector<GURL> gurls_;
+  const WebURL url1_;
+  const WebURL url2_;
+  const WebVector<WebURL> urls_;
+  const WebString presentation_id_;
+  const WebArrayBuffer array_buffer_;
+  MockPresentationAvailabilityObserver observer_;
+  MockPresentationService presentation_service_;
+  TestPresentationDispatcher dispatcher_;
+
+ private:
+  content::TestBrowserThreadBundle thread_bundle_;
+};
+
+TEST_F(PresentationDispatcherTest, TestStartSession) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(presentation_service_, StartSession(gurls_, _))
+      .WillOnce(Invoke([this](
+          const std::vector<GURL>& presentation_urls,
+          const PresentationService::StartSessionCallback& callback) {
+        PresentationSessionInfoPtr session_info(PresentationSessionInfo::New());
+        session_info->url = gurl1_;
+        session_info->id = presentation_id_.utf8();
+        callback.Run(std::move(session_info), PresentationErrorPtr());
+      }));
+  dispatcher_.startSession(
+      urls_, base::MakeUnique<TestWebPresentationConnectionCallback>(
+                 url1_, presentation_id_));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestStartSessionError) {
+  WebString error_message = WebString::fromUTF8("Test error message");
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(presentation_service_, StartSession(gurls_, _))
+      .WillOnce(Invoke([this, &error_message](
+          const std::vector<GURL>& presentation_urls,
+          const PresentationService::StartSessionCallback& callback) {
+        PresentationErrorPtr error(PresentationError::New());
+        error->error_type = PresentationErrorType::NO_AVAILABLE_SCREENS;
+        error->message = error_message.utf8();
+        callback.Run(PresentationSessionInfoPtr(), std::move(error));
+      }));
+  dispatcher_.startSession(
+      urls_,
+      base::MakeUnique<TestWebPresentationConnectionErrorCallback>(
+          WebPresentationError::ErrorTypeNoAvailableScreens, error_message));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestJoinSessionError) {
+  WebString error_message = WebString::fromUTF8("Test error message");
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(presentation_service_, JoinSession(gurls_, _, _))
+      .WillOnce(Invoke([this, &error_message](
+          const std::vector<GURL>& presentation_urls,
+          const base::Optional<std::string>& presentation_id,
+          const PresentationService::JoinSessionCallback& callback) {
+        EXPECT_TRUE(presentation_id.has_value());
+        EXPECT_EQ(presentation_id_.utf8(), presentation_id.value());
+        PresentationErrorPtr error(PresentationError::New());
+        error->error_type = PresentationErrorType::NO_AVAILABLE_SCREENS;
+        error->message = error_message.utf8();
+        callback.Run(PresentationSessionInfoPtr(), std::move(error));
+      }));
+  dispatcher_.joinSession(
+      urls_, presentation_id_,
+      base::MakeUnique<TestWebPresentationConnectionErrorCallback>(
+          WebPresentationError::ErrorTypeNoAvailableScreens, error_message));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestJoinSession) {
+  base::RunLoop run_loop;
+
+  EXPECT_CALL(presentation_service_, JoinSession(gurls_, _, _))
+      .WillOnce(Invoke([this](
+          const std::vector<GURL>& presentation_urls,
+          const base::Optional<std::string>& presentation_id,
+          const PresentationService::JoinSessionCallback& callback) {
+        EXPECT_TRUE(presentation_id.has_value());
+        EXPECT_EQ(presentation_id_.utf8(), presentation_id.value());
+        PresentationSessionInfoPtr session_info(PresentationSessionInfo::New());
+        session_info->url = gurl1_;
+        session_info->id = presentation_id_.utf8();
+        callback.Run(std::move(session_info), PresentationErrorPtr());
+      }));
+  dispatcher_.joinSession(
+      urls_, presentation_id_,
+      base::MakeUnique<TestWebPresentationConnectionCallback>(
+          url1_, presentation_id_));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestSendString) {
+  WebString message = WebString::fromUTF8("test message");
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_, SendConnectionMessageInternal(_, _, _))
+      .WillOnce(Invoke([this, &message](
+          PresentationSessionInfo* session_info,
+          ConnectionMessage* message_request,
+          const PresentationService::SendConnectionMessageCallback& callback) {
+        EXPECT_EQ(gurl1_, session_info->url);
+        EXPECT_EQ(presentation_id_.utf8(), session_info->id);
+        EXPECT_TRUE(message_request->message.has_value());
+        EXPECT_EQ(message.utf8(), message_request->message.value());
+        callback.Run(true);
+      }));
+  dispatcher_.sendString(url1_, presentation_id_, message);
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestSendArrayBuffer) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_, SendConnectionMessageInternal(_, _, _))
+      .WillOnce(Invoke([this](
+          PresentationSessionInfo* session_info,
+          ConnectionMessage* message_request,
+          const PresentationService::SendConnectionMessageCallback& callback) {
+        EXPECT_EQ(gurl1_, session_info->url);
+        EXPECT_EQ(presentation_id_.utf8(), session_info->id);
+        std::vector<uint8_t> data(
+            array_buffer_data(),
+            array_buffer_data() + array_buffer_.byteLength());
+        EXPECT_TRUE(message_request->data.has_value());
+        EXPECT_EQ(data, message_request->data.value());
+        callback.Run(true);
+      }));
+  dispatcher_.sendArrayBuffer(url1_, presentation_id_, array_buffer_data(),
+                              array_buffer_.byteLength());
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestSendBlobData) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_, SendConnectionMessageInternal(_, _, _))
+      .WillOnce(Invoke([this](
+          PresentationSessionInfo* session_info,
+          ConnectionMessage* message_request,
+          const PresentationService::SendConnectionMessageCallback& callback) {
+        EXPECT_EQ(gurl1_, session_info->url);
+        EXPECT_EQ(presentation_id_.utf8(), session_info->id);
+        std::vector<uint8_t> data(
+            array_buffer_data(),
+            array_buffer_data() + array_buffer_.byteLength());
+        EXPECT_TRUE(message_request->data.has_value());
+        EXPECT_EQ(data, message_request->data.value());
+        callback.Run(true);
+      }));
+  dispatcher_.sendBlobData(url1_, presentation_id_, array_buffer_data(),
+                           array_buffer_.byteLength());
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestCloseSession) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_,
+              CloseConnection(gurl1_, presentation_id_.utf8()));
+  dispatcher_.closeSession(url1_, presentation_id_);
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestTerminateSession) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_,
+              Terminate(gurl1_, presentation_id_.utf8()));
+  dispatcher_.terminateSession(url1_, presentation_id_);
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestListenForScreenAvailability) {
+  base::RunLoop run_loop1;
+  EXPECT_CALL(presentation_service_, ListenForScreenAvailability(gurl1_));
+  dispatcher_.getAvailability(
+      url1_, base::MakeUnique<WebPresentationAvailabilityCallbacks>());
+  dispatcher_.OnScreenAvailabilityUpdated(url1_, true);
+  run_loop1.RunUntilIdle();
+
+  base::RunLoop run_loop2;
+  EXPECT_CALL(presentation_service_, ListenForScreenAvailability(gurl1_));
+  dispatcher_.startListening(&observer_);
+  run_loop2.RunUntilIdle();
+
+  base::RunLoop run_loop3;
+  EXPECT_CALL(observer_, availabilityChanged(false));
+  dispatcher_.OnScreenAvailabilityUpdated(url1_, false);
+  EXPECT_CALL(observer_, availabilityChanged(true));
+  dispatcher_.OnScreenAvailabilityUpdated(url1_, true);
+  EXPECT_CALL(presentation_service_,
+              StopListeningForScreenAvailability(gurl1_));
+  dispatcher_.stopListening(&observer_);
+  run_loop3.RunUntilIdle();
+
+  // After stopListening(), |observer_| should no longer be notified.
+  base::RunLoop run_loop4;
+  EXPECT_CALL(observer_, availabilityChanged(false)).Times(0);
+  dispatcher_.OnScreenAvailabilityUpdated(url1_, false);
+  run_loop4.RunUntilIdle();
+}
+
+TEST_F(PresentationDispatcherTest, TestSetDefaultPresentationUrls) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(presentation_service_, SetDefaultPresentationUrls(gurls_));
+  dispatcher_.setDefaultPresentationUrls(urls_);
+  run_loop.RunUntilIdle();
+}
+
+}  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 8b795cf..e07a00d 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1357,6 +1357,7 @@
     "../renderer/media/video_capture_impl_unittest.cc",
     "../renderer/media/webmediaplayer_ms_unittest.cc",
     "../renderer/peripheral_content_heuristic_unittest.cc",
+    "../renderer/presentation/presentation_dispatcher_unittest.cc",
     "../renderer/render_thread_impl_unittest.cc",
     "../renderer/render_widget_unittest.cc",
     "../renderer/scheduler/resource_dispatch_throttler_unittest.cc",
diff --git a/extensions/renderer/api_event_handler_unittest.cc b/extensions/renderer/api_event_handler_unittest.cc
index ce19937..e43c860 100644
--- a/extensions/renderer/api_event_handler_unittest.cc
+++ b/extensions/renderer/api_event_handler_unittest.cc
@@ -447,4 +447,63 @@
                 context->Global(), context, "eventArgs"));
 }
 
+// Test listeners that remove themselves in their handling of the event.
+TEST_F(APIEventHandlerTest, RemovingListenersWhileHandlingEvent) {
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = ContextLocal();
+
+  APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult));
+  const char kEventName[] = "alpha";
+  v8::Local<v8::Object> event =
+      handler.CreateEventInstance(kEventName, context);
+  ASSERT_FALSE(event.IsEmpty());
+
+  {
+    // Cache the event object on the global in order to allow for easy removal.
+    v8::Local<v8::Function> set_event_on_global =
+        FunctionFromString(
+            context,
+           "(function(event) { this.testEvent = event; })");
+    v8::Local<v8::Value> args[] = {event};
+    RunFunctionOnGlobal(set_event_on_global, context, arraysize(args), args);
+    EXPECT_EQ(event,
+              GetPropertyFromObject(context->Global(), context, "testEvent"));
+  }
+
+  // A listener function that removes itself as a listener.
+  const char kListenerFunction[] =
+      "(function() {\n"
+      "  return function listener() {\n"
+      "    this.testEvent.removeListener(listener);\n"
+      "  };\n"
+      "})();";
+
+  // Create and add a bunch of listeners.
+  std::vector<v8::Local<v8::Function>> listeners;
+  const size_t kNumListeners = 20u;
+  listeners.reserve(kNumListeners);
+  for (size_t i = 0; i < kNumListeners; ++i)
+    listeners.push_back(FunctionFromString(context, kListenerFunction));
+
+  const char kAddListenerFunction[] =
+      "(function(event, listener) { event.addListener(listener); })";
+  v8::Local<v8::Function> add_listener_function =
+      FunctionFromString(context, kAddListenerFunction);
+
+  for (const auto& listener : listeners) {
+    v8::Local<v8::Value> argv[] = {event, listener};
+    RunFunctionOnGlobal(add_listener_function, context, arraysize(argv), argv);
+  }
+
+  // Fire the event. All listeners should be removed (and we shouldn't crash).
+  EXPECT_EQ(kNumListeners,
+            handler.GetNumEventListenersForTesting(kEventName, context));
+  handler.FireEventInContext(kEventName, context, base::ListValue());
+  EXPECT_EQ(0u, handler.GetNumEventListenersForTesting(kEventName, context));
+
+  // TODO(devlin): Another possible test: register listener a and listener b,
+  // where a removes b and b removes a. Theoretically, only one should be
+  // notified. Investigate what we currently do in JS-style bindings.
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/event_emitter.cc b/extensions/renderer/event_emitter.cc
index 74245a5..afc358a 100644
--- a/extensions/renderer/event_emitter.cc
+++ b/extensions/renderer/event_emitter.cc
@@ -34,10 +34,15 @@
 
 void EventEmitter::Fire(v8::Local<v8::Context> context,
                         std::vector<v8::Local<v8::Value>>* args) {
-  for (const auto& listener : listeners_) {
-    run_js_.Run(listener.Get(context->GetIsolate()), context, args->size(),
-                             args->data());
-  }
+  // We create a local copy of listeners_ since the array can be modified during
+  // handling.
+  std::vector<v8::Local<v8::Function>> listeners;
+  listeners.reserve(listeners_.size());
+  for (const auto& listener : listeners_)
+    listeners.push_back(listener.Get(context->GetIsolate()));
+
+  for (const auto& listener : listeners)
+    run_js_.Run(listener, context, args->size(), args->data());
 }
 
 void EventEmitter::AddListener(gin::Arguments* arguments) {
diff --git a/infra/config/cq.cfg b/infra/config/cq.cfg
index 5a494ce..549191b 100644
--- a/infra/config/cq.cfg
+++ b/infra/config/cq.cfg
@@ -57,6 +57,10 @@
         experiment_percentage: 100
       }
       builders { name: "linux_chromium_rel_ng" }
+      builders {
+        name: "linux_chromium_tsan_rel_ng"
+        experiment_percentage: 10
+      }
     }
     buckets {
       name: "master.tryserver.chromium.mac"
diff --git a/ios/chrome/browser/ui/BUILD.gn b/ios/chrome/browser/ui/BUILD.gn
index 83ee9d9..58f64a1 100644
--- a/ios/chrome/browser/ui/BUILD.gn
+++ b/ios/chrome/browser/ui/BUILD.gn
@@ -4,19 +4,7 @@
 
 import("//build/config/ios/rules.gni")
 
-source_set("ui_arc") {
-  sources = [
-    "network_activity_indicator_manager.h",
-    "network_activity_indicator_manager.mm",
-  ]
-  deps = [
-    "//base",
-  ]
-  configs += [ "//build/config/compiler:enable_arc" ]
-}
-
 source_set("ui") {
-  configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
     "UIView+SizeClassSupport.h",
     "UIView+SizeClassSupport.mm",
@@ -32,6 +20,8 @@
     "image_util.mm",
     "native_content_controller.h",
     "native_content_controller.mm",
+    "network_activity_indicator_manager.h",
+    "network_activity_indicator_manager.mm",
     "orientation_limiting_navigation_controller.h",
     "orientation_limiting_navigation_controller.mm",
     "prerender_final_status.h",
@@ -61,18 +51,13 @@
     "//ui/base",
     "//ui/gfx",
   ]
-  public_deps = [
-    ":ui_arc",
-  ]
-  allow_circular_includes_from = [
-    ":ui_arc",
-    "//ios/chrome/browser/ui/commands",
-  ]
+  allow_circular_includes_from = [ "//ios/chrome/browser/ui/commands" ]
   libs = [
     "Accelerate.framework",
     "CoreGraphics.framework",
     "QuartzCore.framework",
   ]
+  configs += [ "//build/config/compiler:enable_arc" ]
 }
 
 source_set("unit_tests") {
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index b2e4514d..77afc2c 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -302,6 +302,7 @@
   testonly = true
   sources = [
     "audio_input_controller_unittest.cc",
+    "audio_input_device_unittest.cc",
     "audio_input_unittest.cc",
     "audio_manager_unittest.cc",
     "audio_output_controller_unittest.cc",
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc
index cee639bb..9ebe480 100644
--- a/media/audio/audio_input_device.cc
+++ b/media/audio/audio_input_device.cc
@@ -178,18 +178,25 @@
       break;
     case AUDIO_INPUT_IPC_DELEGATE_STATE_ERROR:
       DLOG(WARNING) << "AudioInputDevice::OnStateChanged(ERROR)";
-      // Don't dereference the callback object if the audio thread
-      // is stopped or stopping.  That could mean that the callback
-      // object has been deleted.
-      // TODO(tommi): Add an explicit contract for clearing the callback
-      // object.  Possibly require calling Initialize again or provide
-      // a callback object via Start() and clear it in Stop().
-      {
+      if (state_ == CREATING_STREAM) {
+        // At this point, we haven't attempted to start the audio thread.
+        // Accessing the hardware might have failed or we may have reached
+        // the limit of the number of allowed concurrent streams.
+        // We must report the error to the |callback_| so that a potential
+        // audio source object will enter the correct state (e.g. 'ended' for
+        // a local audio source).
+        callback_->OnCaptureError(
+            "Maximum allowed input device limit reached or OS failure.");
+      } else {
+        // Don't dereference the callback object if the audio thread
+        // is stopped or stopping.  That could mean that the callback
+        // object has been deleted.
+        // TODO(tommi): Add an explicit contract for clearing the callback
+        // object.  Possibly require calling Initialize again or provide
+        // a callback object via Start() and clear it in Stop().
         base::AutoLock auto_lock_(audio_thread_lock_);
-        if (audio_thread_) {
-          callback_->OnCaptureError(
-              "AudioInputDevice::OnStateChanged - audio thread still running");
-        }
+        if (audio_thread_)
+          callback_->OnCaptureError("AUDIO_INPUT_IPC_DELEGATE_STATE_ERROR");
       }
       break;
     default:
diff --git a/media/audio/audio_input_device_unittest.cc b/media/audio/audio_input_device_unittest.cc
new file mode 100644
index 0000000..046d2eb8
--- /dev/null
+++ b/media/audio/audio_input_device_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 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/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_input_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DoAll;
+
+namespace media {
+
+namespace {
+
+class MockAudioInputIPC : public AudioInputIPC {
+ public:
+  MockAudioInputIPC() {}
+  ~MockAudioInputIPC() override {}
+
+  MOCK_METHOD5(CreateStream,
+               void(AudioInputIPCDelegate* delegate,
+                    int session_id,
+                    const AudioParameters& params,
+                    bool automatic_gain_control,
+                    uint32_t total_segments));
+  MOCK_METHOD0(RecordStream, void());
+  MOCK_METHOD1(SetVolume, void(double volume));
+  MOCK_METHOD0(CloseStream, void());
+};
+
+class MockCaptureCallback : public AudioCapturerSource::CaptureCallback {
+ public:
+  MockCaptureCallback() {}
+  ~MockCaptureCallback() override {}
+
+  MOCK_METHOD4(Capture,
+               void(const AudioBus* audio_source,
+                    int audio_delay_milliseconds,
+                    double volume,
+                    bool key_pressed));
+
+  MOCK_METHOD1(OnCaptureError, void(const std::string& message));
+};
+
+// Used to terminate a loop from a different thread than the loop belongs to.
+// |task_runner| should be a SingleThreadTaskRunner.
+ACTION_P(QuitLoop, task_runner) {
+  task_runner->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+}
+
+}  // namespace.
+
+// Regular construction.
+TEST(AudioInputDeviceTest, Noop) {
+  base::MessageLoopForIO io_loop;
+  MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+  scoped_refptr<AudioInputDevice> device(
+      new AudioInputDevice(base::WrapUnique(input_ipc), io_loop.task_runner()));
+}
+
+ACTION_P2(ReportStateChange, device, state) {
+  static_cast<AudioInputIPCDelegate*>(device)->OnStateChanged(state);
+}
+
+// Verify that we get an OnCaptureError() callback if CreateStream fails.
+TEST(AudioInputDeviceTest, FailToCreateStream) {
+  AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+                         CHANNEL_LAYOUT_STEREO, 48000, 16, 480);
+
+  base::MessageLoopForIO io_loop;
+  MockCaptureCallback callback;
+  MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+  scoped_refptr<AudioInputDevice> device(
+      new AudioInputDevice(base::WrapUnique(input_ipc), io_loop.task_runner()));
+  device->Initialize(params, &callback, 1);
+  device->Start();
+  EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _, _))
+      .WillOnce(ReportStateChange(device.get(),
+                                  AUDIO_INPUT_IPC_DELEGATE_STATE_ERROR));
+  EXPECT_CALL(callback, OnCaptureError(_))
+      .WillOnce(QuitLoop(io_loop.task_runner()));
+  base::RunLoop().Run();
+}
+
+}  // namespace media.
diff --git a/media/capture/video/android/java/src/org/chromium/media/VideoCapture.java b/media/capture/video/android/java/src/org/chromium/media/VideoCapture.java
index c312140..32c8b2c 100644
--- a/media/capture/video/android/java/src/org/chromium/media/VideoCapture.java
+++ b/media/capture/video/android/java/src/org/chromium/media/VideoCapture.java
@@ -13,6 +13,9 @@
 import org.chromium.base.annotations.JNINamespace;
 
 import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 
 /**
  * Video Capture Device base class, defines a set of methods that native code
@@ -21,6 +24,19 @@
  **/
 @JNINamespace("media")
 public abstract class VideoCapture {
+    /**
+     * Common class for storing a framerate range. Values should be multiplied by 1000.
+     */
+    protected static class FramerateRange {
+        public int min;
+        public int max;
+
+        public FramerateRange(int min, int max) {
+            this.min = min;
+            this.max = max;
+        }
+    }
+
     // The angle (0, 90, 180, 270) that the image needs to be rotated to show in
     // the display's native orientation.
     protected int mCameraNativeOrientation;
@@ -58,7 +74,7 @@
     /**
     * @param zoom Zoom level, should be ignored if 0.
     * @param focusMode Focus mode following AndroidMeteringMode enum.
-    * @param exposureMode Focus mode following AndroidMeteringMode enum.
+    * @param exposureMode Exposure mode following AndroidMeteringMode enum.
     * @param pointsOfInterest2D 2D normalized points of interest, marshalled with
     * x coordinate first followed by the y coordinate.
     * @param hasExposureCompensation Indicates if |exposureCompensation| is set.
@@ -147,6 +163,52 @@
         return orientation;
     }
 
+    /**
+     * Finds the framerate range matching |targetFramerate|. Tries to find a range with as low of a
+     * minimum value as possible to allow the camera adjust based on the lighting conditions.
+     * Assumes that all framerate values are multiplied by 1000.
+     *
+     * This code is mostly copied from WebRTC:
+     * CameraEnumerationAndroid.getClosestSupportedFramerateRange
+     * in webrtc/api/android/java/src/org/webrtc/CameraEnumerationAndroid.java
+     */
+    protected static FramerateRange getClosestFramerateRange(
+            final List<FramerateRange> framerateRanges, final int targetFramerate) {
+        return Collections.min(framerateRanges, new Comparator<FramerateRange>() {
+            // Threshold and penalty weights if the upper bound is further away than
+            // |MAX_FPS_DIFF_THRESHOLD| from requested.
+            private static final int MAX_FPS_DIFF_THRESHOLD = 5000;
+            private static final int MAX_FPS_LOW_DIFF_WEIGHT = 1;
+            private static final int MAX_FPS_HIGH_DIFF_WEIGHT = 3;
+
+            // Threshold and penalty weights if the lower bound is bigger than |MIN_FPS_THRESHOLD|.
+            private static final int MIN_FPS_THRESHOLD = 8000;
+            private static final int MIN_FPS_LOW_VALUE_WEIGHT = 1;
+            private static final int MIN_FPS_HIGH_VALUE_WEIGHT = 4;
+
+            // Use one weight for small |value| less than |threshold|, and another weight above.
+            private int progressivePenalty(
+                    int value, int threshold, int lowWeight, int highWeight) {
+                return (value < threshold)
+                        ? value * lowWeight
+                        : threshold * lowWeight + (value - threshold) * highWeight;
+            }
+
+            int diff(FramerateRange range) {
+                final int minFpsError = progressivePenalty(range.min, MIN_FPS_THRESHOLD,
+                        MIN_FPS_LOW_VALUE_WEIGHT, MIN_FPS_HIGH_VALUE_WEIGHT);
+                final int maxFpsError = progressivePenalty(Math.abs(targetFramerate - range.max),
+                        MAX_FPS_DIFF_THRESHOLD, MAX_FPS_LOW_DIFF_WEIGHT, MAX_FPS_HIGH_DIFF_WEIGHT);
+                return minFpsError + maxFpsError;
+            }
+
+            @Override
+            public int compare(FramerateRange range1, FramerateRange range2) {
+                return diff(range1) - diff(range2);
+            }
+        });
+    }
+
     // Method for VideoCapture implementations to call back native code.
     public native void nativeOnFrameAvailable(
             long nativeVideoCaptureDeviceAndroid, byte[] data, int length, int rotation);
diff --git a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera.java b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera.java
index bc2c324..929f04115 100644
--- a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera.java
+++ b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera.java
@@ -287,26 +287,17 @@
             Log.e(TAG, "allocate: no fps range found");
             return false;
         }
+        final ArrayList<FramerateRange> framerateRanges =
+                new ArrayList<FramerateRange>(listFpsRange.size());
+        for (int[] range : listFpsRange) {
+            framerateRanges.add(new FramerateRange(range[0], range[1]));
+        }
         // API fps ranges are scaled up x1000 to avoid floating point.
         int frameRateScaled = frameRate * 1000;
-        // Use the first range as the default chosen range.
-        int[] chosenFpsRange = listFpsRange.get(0);
-        int frameRateNearest = Math.abs(frameRateScaled - chosenFpsRange[0])
-                        < Math.abs(frameRateScaled - chosenFpsRange[1])
-                ? chosenFpsRange[0]
-                : chosenFpsRange[1];
-        int chosenFrameRate = (frameRateNearest + 999) / 1000;
-        int fpsRangeSize = Integer.MAX_VALUE;
-        for (int[] fpsRange : listFpsRange) {
-            if (fpsRange[0] <= frameRateScaled && frameRateScaled <= fpsRange[1]
-                    && (fpsRange[1] - fpsRange[0]) <= fpsRangeSize) {
-                chosenFpsRange = fpsRange;
-                chosenFrameRate = frameRate;
-                fpsRangeSize = fpsRange[1] - fpsRange[0];
-            }
-        }
-        Log.d(TAG, "allocate: fps set to %d, [%d-%d]", chosenFrameRate, chosenFpsRange[0],
-                chosenFpsRange[1]);
+        final FramerateRange chosenFramerateRange =
+                getClosestFramerateRange(framerateRanges, frameRateScaled);
+        final int[] chosenFpsRange = new int[] {chosenFramerateRange.min, chosenFramerateRange.max};
+        Log.d(TAG, "allocate: fps set to [%d-%d]", chosenFpsRange[0], chosenFpsRange[1]);
 
         // Calculate size.
         List<android.hardware.Camera.Size> listCameraSize = parameters.getSupportedPreviewSizes();
@@ -347,9 +338,8 @@
             Log.d(TAG, "Continuous focus mode not supported.");
         }
 
-        // Fill the capture format.
-        mCaptureFormat = new VideoCaptureFormat(
-                matchedWidth, matchedHeight, chosenFrameRate, BuggyDeviceHack.getImageFormat());
+        mCaptureFormat = new VideoCaptureFormat(matchedWidth, matchedHeight,
+                chosenFpsRange[1] / 1000, BuggyDeviceHack.getImageFormat());
         parameters.setPictureSize(matchedWidth, matchedHeight);
         parameters.setPreviewSize(matchedWidth, matchedHeight);
         parameters.setPreviewFpsRange(chosenFpsRange[0], chosenFpsRange[1]);
diff --git a/net/http2/hpack/decoder/hpack_decoder_tables.cc b/net/http2/hpack/decoder/hpack_decoder_tables.cc
new file mode 100644
index 0000000..424cfe4
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_decoder_tables.cc
@@ -0,0 +1,117 @@
+// 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 "net/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include "base/logging.h"
+
+namespace net {
+namespace {
+
+std::vector<HpackStringPair>* MakeStaticTable() {
+  auto ptr = new std::vector<HpackStringPair>();
+  ptr->reserve(kFirstDynamicTableIndex);
+  ptr->emplace_back("", "");
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(ptr->size(), index);               \
+  ptr->emplace_back(name, value)
+
+#include "net/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return ptr;
+}
+
+const std::vector<HpackStringPair>* GetStaticTable() {
+  static const std::vector<HpackStringPair>* const g_static_table =
+      MakeStaticTable();
+  return g_static_table;
+}
+
+}  // namespace
+
+HpackDecoderStaticTable::HpackDecoderStaticTable(
+    const std::vector<HpackStringPair>* table)
+    : table_(table) {}
+
+HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {}
+
+const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const {
+  if (0 < index && index < kFirstDynamicTableIndex) {
+    return &((*table_)[index]);
+  }
+  return nullptr;
+}
+
+HpackDecoderDynamicTable::HpackDecoderDynamicTable() {}
+HpackDecoderDynamicTable::~HpackDecoderDynamicTable() {}
+
+void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) {
+  EnsureSizeNoMoreThan(size_limit);
+  DCHECK_LE(current_size_, size_limit);
+  size_limit_ = size_limit;
+}
+
+// TODO(jamessynge): Check somewhere before here that names received from the
+// peer are valid (e.g. are lower-case, no whitespace, etc.).
+bool HpackDecoderDynamicTable::Insert(const HpackString& name,
+                                      const HpackString& value) {
+  HpackStringPair p(name, value);
+  size_t entry_size = p.size();
+  DVLOG(2) << "InsertEntry of size=" << entry_size << "\n     name: " << name
+           << "\n    value: " << value;
+  if (entry_size > size_limit_) {
+    DVLOG(2) << "InsertEntry: entry larger than table, removing "
+             << table_.size() << " entries, of total size " << current_size_
+             << " bytes.";
+    table_.clear();
+    current_size_ = 0;
+    return false;  // Not inserted because too large.
+  }
+  size_t insert_limit = size_limit_ - entry_size;
+  EnsureSizeNoMoreThan(insert_limit);
+  table_.push_front(p);
+  current_size_ += entry_size;
+  DVLOG(2) << "InsertEntry: current_size_=" << current_size_;
+  DCHECK_GE(current_size_, entry_size);
+  DCHECK_LE(current_size_, size_limit_);
+  return true;
+}
+
+const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const {
+  if (index < table_.size()) {
+    return &(table_[index]);
+  }
+  return nullptr;
+}
+
+void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) {
+  DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit
+           << ", current_size_=" << current_size_;
+  // Not the most efficient choice, but any easy way to start.
+  while (current_size_ > limit) {
+    RemoveLastEntry();
+  }
+  DCHECK_LE(current_size_, limit);
+}
+
+void HpackDecoderDynamicTable::RemoveLastEntry() {
+  DCHECK(!table_.empty());
+  if (!table_.empty()) {
+    DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_
+             << ", last entry size=" << table_.back().size();
+    DCHECK_GE(current_size_, table_.back().size());
+    current_size_ -= table_.back().size();
+    table_.pop_back();
+    // Empty IFF current_size_ == 0.
+    DCHECK_EQ(table_.empty(), current_size_ == 0);
+  }
+}
+
+HpackDecoderTables::HpackDecoderTables() {}
+HpackDecoderTables::~HpackDecoderTables() {}
+
+}  // namespace net
diff --git a/net/http2/hpack/decoder/hpack_decoder_tables.h b/net/http2/hpack/decoder/hpack_decoder_tables.h
new file mode 100644
index 0000000..525a54e7
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_decoder_tables.h
@@ -0,0 +1,152 @@
+// 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 NET_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+#define NET_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+
+// Static and dynamic tables for the HPACK decoder. See:
+// http://httpwg.org/specs/rfc7541.html#indexing.tables
+
+// Note that the Lookup methods return nullptr if the requested index was not
+// found. This should be treated as a COMPRESSION error according to the HTTP/2
+// spec, which is a connection level protocol error (i.e. the connection must
+// be terminated). See these sections in the two RFCs:
+// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+// http://httpwg.org/specs/rfc7541.html#index.address.space
+// http://httpwg.org/specs/rfc7540.html#HeaderBlock
+
+#include <stddef.h>
+
+#include <deque>
+#include <functional>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/base/net_export.h"
+#include "net/http2/hpack/hpack_string.h"
+#include "net/http2/http2_constants.h"
+
+namespace net {
+namespace test {
+class HpackDecoderTablesPeer;
+}  // namespace test
+
+const size_t kFirstDynamicTableIndex = 62;
+
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the
+// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for
+// info about accessing the static table.
+class NET_EXPORT_PRIVATE HpackDecoderStaticTable {
+ public:
+  explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table);
+  // Uses a global table shared by all threads.
+  HpackDecoderStaticTable();
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  const std::vector<HpackStringPair>* const table_;
+};
+
+// HpackDecoderDynamicTable implements HPACK compression feature "indexed
+// headers"; previously sent headers may be referenced later by their index
+// in the dynamic table. See these sections of the RFC:
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table
+//   http://httpwg.org/specs/rfc7541.html#dynamic.table.management
+class NET_EXPORT_PRIVATE HpackDecoderDynamicTable {
+ public:
+  HpackDecoderDynamicTable();
+  ~HpackDecoderDynamicTable();
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit);
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  bool Insert(const HpackString& name, const HpackString& value);
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const;
+
+  size_t size_limit() const { return size_limit_; }
+  size_t current_size() const { return current_size_; }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+
+  // Drop older entries to ensure the size is not greater than limit.
+  void EnsureSizeNoMoreThan(size_t limit);
+
+  // Removes the oldest dynamic table entry.
+  void RemoveLastEntry();
+
+  // The last received DynamicTableSizeUpdate value, initialized to
+  // SETTINGS_HEADER_TABLE_SIZE.
+  size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize();
+
+  size_t current_size_ = 0;
+
+  std::deque<HpackStringPair> table_;
+
+  DISALLOW_COPY_AND_ASSIGN(HpackDecoderDynamicTable);
+};
+
+class NET_EXPORT_PRIVATE HpackDecoderTables {
+ public:
+  HpackDecoderTables();
+  ~HpackDecoderTables();
+
+  // Sets a new size limit, received from the peer; performs evictions if
+  // necessary to ensure that the current size does not exceed the new limit.
+  // The caller needs to have validated that size_limit does not
+  // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+  void DynamicTableSizeUpdate(size_t size_limit) {
+    dynamic_table_.DynamicTableSizeUpdate(size_limit);
+  }
+
+  // Returns true if inserted, false if too large (at which point the
+  // dynamic table will be empty.)
+  // TODO(jamessynge): Add methods for moving the string(s) into the table,
+  // or for otherwise avoiding unnecessary copies.
+  bool Insert(const HpackString& name, const HpackString& value) {
+    return dynamic_table_.Insert(name, value);
+  }
+
+  // If index is valid, returns a pointer to the entry, otherwise returns
+  // nullptr.
+  const HpackStringPair* Lookup(size_t index) const {
+    if (index < kFirstDynamicTableIndex) {
+      return static_table_.Lookup(index);
+    } else {
+      return dynamic_table_.Lookup(index - kFirstDynamicTableIndex);
+    }
+  }
+
+  // The size limit that the peer (the HPACK encoder) has told the decoder it is
+  // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096.
+  size_t header_table_size_limit() const { return dynamic_table_.size_limit(); }
+
+  // Sum of the sizes of the dynamic table entries.
+  size_t current_header_table_size() const {
+    return dynamic_table_.current_size();
+  }
+
+ private:
+  friend class test::HpackDecoderTablesPeer;
+  HpackDecoderStaticTable static_table_;
+  HpackDecoderDynamicTable dynamic_table_;
+
+  DISALLOW_COPY_AND_ASSIGN(HpackDecoderTables);
+};
+
+}  // namespace net
+
+#endif  // NET_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
diff --git a/net/http2/hpack/decoder/hpack_decoder_tables_test.cc b/net/http2/hpack/decoder/hpack_decoder_tables_test.cc
new file mode 100644
index 0000000..13bdb6f
--- /dev/null
+++ b/net/http2/hpack/decoder/hpack_decoder_tables_test.cc
@@ -0,0 +1,265 @@
+// 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 "net/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include <algorithm>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/http2/tools/failure.h"
+#include "net/http2/tools/http2_random.h"
+#include "net/http2/tools/random_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using std::string;
+
+namespace net {
+namespace test {
+class HpackDecoderTablesPeer {
+ public:
+  static size_t num_dynamic_entries(const HpackDecoderTables& tables) {
+    return tables.dynamic_table_.table_.size();
+  }
+};
+
+namespace {
+struct StaticEntry {
+  const char* name;
+  const char* value;
+  size_t index;
+};
+
+std::vector<StaticEntry> MakeSpecStaticEntries() {
+  std::vector<StaticEntry> static_entries;
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+  DCHECK_EQ(static_entries.size() + 1, index); \
+  static_entries.push_back({name, value, index});
+
+#include "net/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+  return static_entries;
+}
+
+template <class C>
+void ShuffleCollection(C* collection, RandomBase* r) {
+  std::shuffle(collection->begin(), collection->end(), *r);
+}
+
+class HpackDecoderStaticTableTest : public ::testing::Test {
+ protected:
+  HpackDecoderStaticTableTest() {}
+
+  std::vector<StaticEntry> shuffled_static_entries() {
+    std::vector<StaticEntry> entries = MakeSpecStaticEntries();
+    ShuffleCollection(&entries, &random_);
+    return entries;
+  }
+
+  // This test is in a function so that it can be applied to both the static
+  // table and the combined static+dynamic tables.
+  AssertionResult VerifyStaticTableContents() {
+    for (const auto& expected : shuffled_static_entries()) {
+      const HpackStringPair* found = Lookup(expected.index);
+      VERIFY_NE(found, nullptr);
+      VERIFY_EQ(expected.name, found->name) << expected.index;
+      VERIFY_EQ(expected.value, found->value) << expected.index;
+    }
+
+    // There should be no entry with index 0.
+    VERIFY_EQ(nullptr, Lookup(0));
+    return AssertionSuccess();
+  }
+
+  virtual const HpackStringPair* Lookup(size_t index) {
+    return static_table_.Lookup(index);
+  }
+
+  RandomBase* RandomPtr() { return &random_; }
+
+ private:
+  Http2Random random_;
+  HpackDecoderStaticTable static_table_;
+};
+
+TEST_F(HpackDecoderStaticTableTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+size_t Size(const string& name, const string& value) {
+  return name.size() + value.size() + 32;
+}
+
+// To support tests with more than a few of hand crafted changes to the dynamic
+// table, we have another, exceedingly simple, implementation of the HPACK
+// dynamic table containing FakeHpackEntry instances. We can thus compare the
+// contents of the actual table with those in fake_dynamic_table_.
+
+typedef std::tuple<string, string, size_t> FakeHpackEntry;
+const string& Name(const FakeHpackEntry& entry) {
+  return std::get<0>(entry);
+}
+const string& Value(const FakeHpackEntry& entry) {
+  return std::get<1>(entry);
+}
+size_t Size(const FakeHpackEntry& entry) {
+  return std::get<2>(entry);
+}
+
+class HpackDecoderTablesTest : public HpackDecoderStaticTableTest {
+ protected:
+  const HpackStringPair* Lookup(size_t index) override {
+    return tables_.Lookup(index);
+  }
+
+  size_t dynamic_size_limit() const {
+    return tables_.header_table_size_limit();
+  }
+  size_t current_dynamic_size() const {
+    return tables_.current_header_table_size();
+  }
+  size_t num_dynamic_entries() const {
+    return HpackDecoderTablesPeer::num_dynamic_entries(tables_);
+  }
+
+  // Insert the name and value into fake_dynamic_table_.
+  void FakeInsert(const string& name, const string& value) {
+    FakeHpackEntry entry(name, value, Size(name, value));
+    fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry);
+  }
+
+  // Add up the size of all entries in fake_dynamic_table_.
+  size_t FakeSize() {
+    size_t sz = 0;
+    for (const auto& entry : fake_dynamic_table_) {
+      sz += Size(entry);
+    }
+    return sz;
+  }
+
+  // If the total size of the fake_dynamic_table_ is greater than limit,
+  // keep the first N entries such that those N entries have a size not
+  // greater than limit, and such that keeping entry N+1 would have a size
+  // greater than limit. Returns the count of removed bytes.
+  size_t FakeTrim(size_t limit) {
+    size_t original_size = FakeSize();
+    size_t total_size = 0;
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      total_size += Size(fake_dynamic_table_[ndx]);
+      if (total_size > limit) {
+        // Need to get rid of ndx and all following entries.
+        fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx,
+                                  fake_dynamic_table_.end());
+        return original_size - FakeSize();
+      }
+    }
+    return 0;
+  }
+
+  // Verify that the contents of the actual dynamic table match those in
+  // fake_dynamic_table_.
+  AssertionResult VerifyDynamicTableContents() {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+
+    for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+      const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex);
+      VERIFY_NE(found, nullptr);
+
+      const auto& expected = fake_dynamic_table_[ndx];
+      VERIFY_EQ(Name(expected), found->name);
+      VERIFY_EQ(Value(expected), found->value);
+    }
+
+    // Make sure there are no more entries.
+    VERIFY_EQ(nullptr,
+              Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex));
+    return AssertionSuccess();
+  }
+
+  // Apply an update to the limit on the maximum size of the dynamic table.
+  AssertionResult DynamicTableSizeUpdate(size_t size_limit) {
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    if (size_limit < current_dynamic_size()) {
+      // Will need to trim the dynamic table's oldest entries.
+      tables_.DynamicTableSizeUpdate(size_limit);
+      FakeTrim(size_limit);
+      return VerifyDynamicTableContents();
+    }
+    // Shouldn't change the size.
+    tables_.DynamicTableSizeUpdate(size_limit);
+    return VerifyDynamicTableContents();
+  }
+
+  // Insert an entry into the dynamic table, confirming that trimming of entries
+  // occurs if the total size is greater than the limit, and that older entries
+  // move up by 1 index.
+  AssertionResult Insert(const string& name, const string& value) {
+    size_t old_count = num_dynamic_entries();
+    if (tables_.Insert(HpackString(name), HpackString(value))) {
+      VERIFY_GT(current_dynamic_size(), 0u);
+      VERIFY_GT(num_dynamic_entries(), 0u);
+    } else {
+      VERIFY_EQ(current_dynamic_size(), 0u);
+      VERIFY_EQ(num_dynamic_entries(), 0u);
+    }
+    FakeInsert(name, value);
+    VERIFY_EQ(old_count + 1, fake_dynamic_table_.size());
+    FakeTrim(dynamic_size_limit());
+    VERIFY_EQ(current_dynamic_size(), FakeSize());
+    VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+    return VerifyDynamicTableContents();
+  }
+
+ private:
+  HpackDecoderTables tables_;
+
+  std::vector<FakeHpackEntry> fake_dynamic_table_;
+};
+
+TEST_F(HpackDecoderTablesTest, StaticTableContents) {
+  EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+// Generate a bunch of random header entries, insert them, and confirm they
+// present, as required by the RFC, using VerifyDynamicTableContents above on
+// each Insert. Also apply various resizings of the dynamic table.
+TEST_F(HpackDecoderTablesTest, RandomDynamicTable) {
+  EXPECT_EQ(0u, current_dynamic_size());
+  EXPECT_TRUE(VerifyStaticTableContents());
+  EXPECT_TRUE(VerifyDynamicTableContents());
+
+  std::vector<size_t> table_sizes;
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(dynamic_size_limit());
+  table_sizes.push_back(dynamic_size_limit() / 2);
+  table_sizes.push_back(0);
+  table_sizes.push_back(dynamic_size_limit());
+
+  for (size_t limit : table_sizes) {
+    ASSERT_TRUE(DynamicTableSizeUpdate(limit));
+    for (int insert_count = 0; insert_count < 100; ++insert_count) {
+      string name = GenerateHttp2HeaderName(
+          GenerateUniformInRange(2, 40, RandomPtr()), RandomPtr());
+      string value = GenerateWebSafeString(
+          GenerateUniformInRange(2, 600, RandomPtr()), RandomPtr());
+      ASSERT_TRUE(Insert(name, value));
+    }
+    EXPECT_TRUE(VerifyStaticTableContents());
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/http2/hpack/hpack_static_table_entries.inc b/net/http2/hpack/hpack_static_table_entries.inc
new file mode 100644
index 0000000..3d2f1087
--- /dev/null
+++ b/net/http2/hpack/hpack_static_table_entries.inc
@@ -0,0 +1,69 @@
+// 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.
+
+// This file is designed to be included by C/C++ files which need the contents
+// of the HPACK static table. It may be included more than once if necessary.
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition
+
+STATIC_TABLE_ENTRY(":authority", "", 1u);
+STATIC_TABLE_ENTRY(":method", "GET", 2u);
+STATIC_TABLE_ENTRY(":method", "POST", 3u);
+STATIC_TABLE_ENTRY(":path", "/", 4u);
+STATIC_TABLE_ENTRY(":path", "/index.html", 5u);
+STATIC_TABLE_ENTRY(":scheme", "http", 6u);
+STATIC_TABLE_ENTRY(":scheme", "https", 7u);
+STATIC_TABLE_ENTRY(":status", "200", 8u);
+STATIC_TABLE_ENTRY(":status", "204", 9u);
+STATIC_TABLE_ENTRY(":status", "206", 10u);
+STATIC_TABLE_ENTRY(":status", "304", 11u);
+STATIC_TABLE_ENTRY(":status", "400", 12u);
+STATIC_TABLE_ENTRY(":status", "404", 13u);
+STATIC_TABLE_ENTRY(":status", "500", 14u);
+STATIC_TABLE_ENTRY("accept-charset", "", 15u);
+STATIC_TABLE_ENTRY("accept-encoding", "gzip, deflate", 16u);
+STATIC_TABLE_ENTRY("accept-language", "", 17u);
+STATIC_TABLE_ENTRY("accept-ranges", "", 18u);
+STATIC_TABLE_ENTRY("accept", "", 19u);
+STATIC_TABLE_ENTRY("access-control-allow-origin", "", 20u);
+STATIC_TABLE_ENTRY("age", "", 21u);
+STATIC_TABLE_ENTRY("allow", "", 22u);
+STATIC_TABLE_ENTRY("authorization", "", 23u);
+STATIC_TABLE_ENTRY("cache-control", "", 24u);
+STATIC_TABLE_ENTRY("content-disposition", "", 25u);
+STATIC_TABLE_ENTRY("content-encoding", "", 26u);
+STATIC_TABLE_ENTRY("content-language", "", 27u);
+STATIC_TABLE_ENTRY("content-length", "", 28u);
+STATIC_TABLE_ENTRY("content-location", "", 29u);
+STATIC_TABLE_ENTRY("content-range", "", 30u);
+STATIC_TABLE_ENTRY("content-type", "", 31u);
+STATIC_TABLE_ENTRY("cookie", "", 32u);
+STATIC_TABLE_ENTRY("date", "", 33u);
+STATIC_TABLE_ENTRY("etag", "", 34u);
+STATIC_TABLE_ENTRY("expect", "", 35u);
+STATIC_TABLE_ENTRY("expires", "", 36u);
+STATIC_TABLE_ENTRY("from", "", 37u);
+STATIC_TABLE_ENTRY("host", "", 38u);
+STATIC_TABLE_ENTRY("if-match", "", 39u);
+STATIC_TABLE_ENTRY("if-modified-since", "", 40u);
+STATIC_TABLE_ENTRY("if-none-match", "", 41u);
+STATIC_TABLE_ENTRY("if-range", "", 42u);
+STATIC_TABLE_ENTRY("if-unmodified-since", "", 43u);
+STATIC_TABLE_ENTRY("last-modified", "", 44u);
+STATIC_TABLE_ENTRY("link", "", 45u);
+STATIC_TABLE_ENTRY("location", "", 46u);
+STATIC_TABLE_ENTRY("max-forwards", "", 47u);
+STATIC_TABLE_ENTRY("proxy-authenticate", "", 48u);
+STATIC_TABLE_ENTRY("proxy-authorization", "", 49u);
+STATIC_TABLE_ENTRY("range", "", 50u);
+STATIC_TABLE_ENTRY("referer", "", 51u);
+STATIC_TABLE_ENTRY("refresh", "", 52u);
+STATIC_TABLE_ENTRY("retry-after", "", 53u);
+STATIC_TABLE_ENTRY("server", "", 54u);
+STATIC_TABLE_ENTRY("set-cookie", "", 55u);
+STATIC_TABLE_ENTRY("strict-transport-security", "", 56u);
+STATIC_TABLE_ENTRY("transfer-encoding", "", 57u);
+STATIC_TABLE_ENTRY("user-agent", "", 58u);
+STATIC_TABLE_ENTRY("vary", "", 59u);
+STATIC_TABLE_ENTRY("via", "", 60u);
+STATIC_TABLE_ENTRY("www-authenticate", "", 61u);
diff --git a/net/http2/hpack/hpack_string.cc b/net/http2/hpack/hpack_string.cc
index f84bedbf..b4b820b 100644
--- a/net/http2/hpack/hpack_string.cc
+++ b/net/http2/hpack/hpack_string.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "base/logging.h"
+
 using base::StringPiece;
 using std::string;
 
@@ -44,4 +46,33 @@
   return out << v.ToString();
 }
 
+HpackStringPair::HpackStringPair(const HpackString& name,
+                                 const HpackString& value)
+    : name(name), value(value) {
+  DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::HpackStringPair(StringPiece name, StringPiece value)
+    : name(name), value(value) {
+  DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::~HpackStringPair() {
+  DVLOG(3) << DebugString() << " dtor";
+}
+
+string HpackStringPair::DebugString() const {
+  string debug_string("HpackStringPair(name=");
+  debug_string.append(name.ToString());
+  debug_string.append(", value=");
+  debug_string.append(value.ToString());
+  debug_string.append(")");
+  return debug_string;
+}
+
+std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) {
+  os << p.DebugString();
+  return os;
+}
+
 }  // namespace net
diff --git a/net/http2/hpack/hpack_string.h b/net/http2/hpack/hpack_string.h
index aeb0c96c..5a7b693 100644
--- a/net/http2/hpack/hpack_string.h
+++ b/net/http2/hpack/hpack_string.h
@@ -30,6 +30,8 @@
   // Not sure yet whether this move ctor is required/sensible.
   HpackString(HpackString&& other) = default;
 
+  HpackString& operator=(const HpackString& other) = default;
+
   ~HpackString();
 
   size_t size() const { return str_.size(); }
@@ -51,6 +53,24 @@
 NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
                                             const HpackString& v);
 
+struct NET_EXPORT_PRIVATE HpackStringPair {
+  HpackStringPair(const HpackString& name, const HpackString& value);
+  HpackStringPair(base::StringPiece name, base::StringPiece value);
+  ~HpackStringPair();
+
+  // Returns the size of a header entry with this name and value, per the RFC:
+  // http://httpwg.org/specs/rfc7541.html#calculating.table.size
+  size_t size() const { return 32 + name.size() + value.size(); }
+
+  std::string DebugString() const;
+
+  HpackString name;
+  HpackString value;
+};
+
+NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
+                                            const HpackStringPair& p);
+
 }  // namespace net
 
 #endif  // NET_HTTP2_HPACK_HPACK_STRING_H_
diff --git a/net/http2/tools/http2_random.cc b/net/http2/tools/http2_random.cc
index 8732d6a8..12dd7436 100644
--- a/net/http2/tools/http2_random.cc
+++ b/net/http2/tools/http2_random.cc
@@ -4,7 +4,6 @@
 
 #include "net/http2/tools/http2_random.h"
 
-#include <limits>
 #include <memory>
 
 #include "base/rand_util.h"
diff --git a/net/http2/tools/http2_random.h b/net/http2/tools/http2_random.h
index f7956cb8..eb3f6aa 100644
--- a/net/http2/tools/http2_random.h
+++ b/net/http2/tools/http2_random.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <string>
 
 namespace net {
@@ -24,8 +25,18 @@
   virtual int32_t Next() = 0;
   virtual int32_t Skewed(int max_log) = 0;
   virtual std::string RandString(int length) = 0;
+
+  // STL UniformRandomNumberGenerator implementation.
+  typedef uint32_t result_type;
+  static constexpr result_type min() { return 0; }
+  static constexpr result_type max() {
+    return std::numeric_limits<uint32_t>::max();
+  }
+  result_type operator()() { return Rand32(); }
 };
 
+// Http2Random holds no state: instances use the same base::RandGenerator
+// with a global state.
 class Http2Random : public RandomBase {
  public:
   ~Http2Random() override {}
diff --git a/net/http2/tools/random_util.cc b/net/http2/tools/random_util.cc
new file mode 100644
index 0000000..b1abebb2
--- /dev/null
+++ b/net/http2/tools/random_util.cc
@@ -0,0 +1,60 @@
+// 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 "net/http2/tools/random_util.h"
+
+#include <cmath>
+
+#include "net/http2/tools/http2_random.h"
+
+using std::string;
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+const char kWebsafe64[] =
+    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+
+string RandomString(RandomBase* rng, int len, StringPiece alphabet) {
+  string random_string;
+  random_string.reserve(len);
+  for (int i = 0; i < len; ++i)
+    random_string.push_back(alphabet[rng->Uniform(alphabet.size())]);
+  return random_string;
+}
+
+size_t GenerateUniformInRange(size_t lo, size_t hi, RandomBase* rng) {
+  if (lo + 1 >= hi) {
+    return lo;
+  }
+  return lo + rng->Rand64() % (hi - lo);
+}
+
+// Here "word" means something that starts with a lower-case letter, and has
+// zero or more additional characters that are numbers or lower-case letters.
+string GenerateHttp2HeaderName(size_t len, RandomBase* rng) {
+  StringPiece alpha_lc = "abcdefghijklmnopqrstuvwxyz";
+  // If the name is short, just make it one word.
+  if (len < 8) {
+    return RandomString(rng, len, alpha_lc);
+  }
+  // If the name is longer, ensure it starts with a word, and after that may
+  // have any character in alphanumdash_lc. 4 is arbitrary, could be as low
+  // as 1.
+  StringPiece alphanumdash_lc = "abcdefghijklmnopqrstuvwxyz0123456789-";
+  return RandomString(rng, 4, alpha_lc) +
+         RandomString(rng, len - 4, alphanumdash_lc);
+}
+
+string GenerateWebSafeString(size_t len, RandomBase* rng) {
+  return RandomString(rng, len, kWebsafe64);
+}
+
+string GenerateWebSafeString(size_t lo, size_t hi, RandomBase* rng) {
+  return GenerateWebSafeString(GenerateUniformInRange(lo, hi, rng), rng);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/http2/tools/random_util.h b/net/http2/tools/random_util.h
new file mode 100644
index 0000000..d29609a
--- /dev/null
+++ b/net/http2/tools/random_util.h
@@ -0,0 +1,39 @@
+// 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 NET_HTTP2_TOOLS_RANDOM_UTIL_H_
+#define NET_HTTP2_TOOLS_RANDOM_UTIL_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace net {
+namespace test {
+
+class RandomBase;
+
+// Returns a random string of length |len|, each character drawn uniformly and
+// independently fom |alphabet|.
+std::string RandomString(RandomBase* rng, int len, base::StringPiece alphabet);
+
+// Returns a random integer in the range [lo, hi).
+size_t GenerateUniformInRange(size_t lo, size_t hi, RandomBase* rng);
+
+// Generate a string with the allowed character set for HTTP/2 / HPACK header
+// names.
+std::string GenerateHttp2HeaderName(size_t len, RandomBase* rng);
+
+// Generate a string with the web-safe string character set of specified len.
+std::string GenerateWebSafeString(size_t len, RandomBase* rng);
+
+// Generate a string with the web-safe string character set of length [lo, hi).
+std::string GenerateWebSafeString(size_t lo, size_t hi, RandomBase* rng);
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_HTTP2_TOOLS_RANDOM_UTIL_H_
diff --git a/net/net.gypi b/net/net.gypi
index ffe6103..61b4c175 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -817,6 +817,8 @@
       'http2/hpack/decoder/hpack_block_decoder.h',
       'http2/hpack/decoder/hpack_decoder_string_buffer.cc',
       'http2/hpack/decoder/hpack_decoder_string_buffer.h',
+      'http2/hpack/decoder/hpack_decoder_tables.cc',
+      'http2/hpack/decoder/hpack_decoder_tables.h',
       'http2/hpack/decoder/hpack_entry_decoder.cc',
       'http2/hpack/decoder/hpack_entry_decoder.h',
       'http2/hpack/decoder/hpack_entry_decoder_listener.cc',
@@ -833,6 +835,7 @@
       'http2/hpack/decoder/hpack_whole_entry_buffer.h',
       'http2/hpack/decoder/hpack_whole_entry_listener.cc',
       'http2/hpack/decoder/hpack_whole_entry_listener.h',
+      'http2/hpack/hpack_static_table_entries.inc',
       'http2/hpack/hpack_string.cc',
       'http2/hpack/hpack_string.h',
       'http2/hpack/http2_hpack_constants.cc',
@@ -1791,6 +1794,7 @@
       'http2/hpack/decoder/hpack_block_collector.h',
       'http2/hpack/decoder/hpack_block_decoder_test.cc',
       'http2/hpack/decoder/hpack_decoder_string_buffer_test.cc',
+      'http2/hpack/decoder/hpack_decoder_tables_test.cc',
       'http2/hpack/decoder/hpack_entry_collector.cc',
       'http2/hpack/decoder/hpack_entry_collector.h',
       'http2/hpack/decoder/hpack_entry_decoder_test.cc',
@@ -1822,6 +1826,8 @@
       'http2/tools/http2_random.h',
       'http2/tools/random_decoder_test.cc',
       'http2/tools/random_decoder_test.h',
+      'http2/tools/random_util.cc',
+      'http2/tools/random_util.h',
       'log/file_net_log_observer_unittest.cc',
       'log/net_log_capture_mode_unittest.cc',
       'log/net_log_unittest.cc',
diff --git a/remoting/BUILD.gn b/remoting/BUILD.gn
index 1ae1485b..ba7a7047 100644
--- a/remoting/BUILD.gn
+++ b/remoting/BUILD.gn
@@ -145,6 +145,7 @@
     "//google_apis",
     "//remoting/base:unit_tests",
     "//remoting/client:unit_tests",
+    "//remoting/client/display:unit_tests",
     "//remoting/protocol:unit_tests",
     "//remoting/signaling:unit_tests",
     "//remoting/test:unit_tests",
diff --git a/remoting/android/BUILD.gn b/remoting/android/BUILD.gn
index 18f7213..bf0969d 100644
--- a/remoting/android/BUILD.gn
+++ b/remoting/android/BUILD.gn
@@ -24,7 +24,7 @@
     "//remoting/android:jni_headers",
     "//remoting/base",
     "//remoting/client",
-    "//remoting/client:opengl_renderer",
+    "//remoting/client/display",
     "//remoting/protocol",
     "//ui/events:dom_keycode_converter",
     "//ui/gfx",
diff --git a/remoting/client/BUILD.gn b/remoting/client/BUILD.gn
index b5ff104..bb8345a 100644
--- a/remoting/client/BUILD.gn
+++ b/remoting/client/BUILD.gn
@@ -73,56 +73,6 @@
   }
 }
 
-source_set("opengl_renderer") {
-  sources = [
-    "gl_canvas.cc",
-    "gl_canvas.h",
-    "gl_cursor.cc",
-    "gl_cursor.h",
-    "gl_cursor_feedback.cc",
-    "gl_cursor_feedback.h",
-    "gl_cursor_feedback_texture.cc",
-    "gl_cursor_feedback_texture.h",
-    "gl_desktop.cc",
-    "gl_desktop.h",
-    "gl_helpers.cc",
-    "gl_helpers.h",
-    "gl_math.cc",
-    "gl_math.h",
-    "gl_render_layer.cc",
-    "gl_render_layer.h",
-    "gl_renderer.cc",
-    "gl_renderer.h",
-  ]
-
-  deps = [
-    "//remoting/proto",
-    "//third_party/libyuv",
-    "//third_party/webrtc/base:rtc_base",
-  ]
-
-  configs += [ "//third_party/khronos:khronos_headers" ]
-
-  if (is_linux) {
-    libs = [ "GL" ]
-  }
-
-  if (is_mac) {
-    libs = [ "OpenGL.framework" ]
-  }
-
-  if (is_ios) {
-    libs = [
-      "GLKit.framework",
-      "OpenGLES.framework",
-    ]
-  }
-
-  if (is_win) {
-    deps += [ "//third_party/angle:libGLESv2" ]
-  }
-}
-
 source_set("unit_tests") {
   testonly = true
 
@@ -159,11 +109,4 @@
     "//testing/gtest",
     "//third_party/webrtc/base:rtc_base",
   ]
-
-  if (!is_win) {
-    # Windows clang builder fails to link the test binary with ANGLE GLESv2.
-    # crbug.com/642027
-    sources += [ "gl_renderer_unittest.cc" ]
-    deps += [ ":opengl_renderer" ]
-  }
 }
diff --git a/remoting/client/display/BUILD.gn b/remoting/client/display/BUILD.gn
new file mode 100644
index 0000000..102850b
--- /dev/null
+++ b/remoting/client/display/BUILD.gn
@@ -0,0 +1,81 @@
+# Copyright 2014 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.
+
+source_set("display") {
+  sources = [
+    "gl_canvas.cc",
+    "gl_canvas.h",
+    "gl_cursor.cc",
+    "gl_cursor.h",
+    "gl_cursor_feedback.cc",
+    "gl_cursor_feedback.h",
+    "gl_cursor_feedback_texture.cc",
+    "gl_cursor_feedback_texture.h",
+    "gl_desktop.cc",
+    "gl_desktop.h",
+    "gl_helpers.cc",
+    "gl_helpers.h",
+    "gl_math.cc",
+    "gl_math.h",
+    "gl_render_layer.cc",
+    "gl_render_layer.h",
+    "gl_renderer.cc",
+    "gl_renderer.h",
+  ]
+
+  deps = [
+    "//remoting/proto",
+    "//third_party/libyuv",
+    "//third_party/webrtc/base:rtc_base",
+  ]
+
+  configs += [ "//third_party/khronos:khronos_headers" ]
+
+  if (is_linux) {
+    libs = [ "GL" ]
+  }
+
+  if (is_mac) {
+    libs = [ "OpenGL.framework" ]
+  }
+
+  if (is_ios) {
+    libs = [
+      "GLKit.framework",
+      "OpenGLES.framework",
+    ]
+  }
+
+  if (is_win) {
+    deps += [ "//third_party/angle:libGLESv2" ]
+  }
+}
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [
+    "gl_renderer_unittest.cc",
+  ]
+
+  configs += [
+    "//remoting/build/config:version",
+    "//remoting/build/config:enable_webrtc_remoting_client",
+  ]
+
+  deps = [
+    ":display",
+    "//remoting/proto",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/webrtc/base:rtc_base",
+  ]
+
+  if (is_win) {
+    # Windows clang builder fails to link the test binary with ANGLE GLESv2.
+    # crbug.com/642027
+    sources -= [ "gl_renderer_unittest.cc" ]
+    deps -= [ ":display" ]
+  }
+}
diff --git a/remoting/client/display/DEPS b/remoting/client/display/DEPS
new file mode 100644
index 0000000..71d244b
--- /dev/null
+++ b/remoting/client/display/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "-remoting/client",
+  "+remoting/client/display",
+]
diff --git a/remoting/client/gl_canvas.cc b/remoting/client/display/gl_canvas.cc
similarity index 91%
rename from remoting/client/gl_canvas.cc
rename to remoting/client/display/gl_canvas.cc
index 0a49f13..31d6b95 100644
--- a/remoting/client/gl_canvas.cc
+++ b/remoting/client/display/gl_canvas.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_canvas.h"
+#include "remoting/client/display/gl_canvas.h"
 
 #include "base/logging.h"
-#include "remoting/client/gl_helpers.h"
-#include "remoting/client/gl_math.h"
+#include "remoting/client/display/gl_helpers.h"
+#include "remoting/client/display/gl_math.h"
 
 namespace {
 
@@ -37,15 +37,15 @@
     "void main() {\n"
     "  v_texCoord = a_texCoord;\n"
 
-       // Transforms coordinates related to the canvas to coordinates
-       // related to the view.
+    // Transforms coordinates related to the canvas to coordinates
+    // related to the view.
     "  vec3 trans_position = u_transform * vec3(a_position, 1.0);\n"
 
-       // Normalize the position by the size of the view.
+    // Normalize the position by the size of the view.
     "  trans_position.xy /= u_viewSize;\n"
 
-       // Transforms texture coordinates to view coordinates and adds
-       // projection component 1.
+    // Transforms texture coordinates to view coordinates and adds
+    // projection component 1.
     "  gl_Position = vec4(tex_to_view * trans_position, 1.0);\n"
     "}";
 
@@ -108,7 +108,7 @@
 void GlCanvas::SetViewSize(int width, int height) {
   DCHECK(width > 0 && height > 0);
   glViewport(0, 0, width, height);
-  float view_size[2] {width, height};
+  float view_size[2]{width, height};
   glUniform2fv(view_size_location_, 1, view_size);
   view_size_set_ = true;
 }
diff --git a/remoting/client/gl_canvas.h b/remoting/client/display/gl_canvas.h
similarity index 94%
rename from remoting/client/gl_canvas.h
rename to remoting/client/display/gl_canvas.h
index 4df0a4b8..975d79bc 100644
--- a/remoting/client/gl_canvas.h
+++ b/remoting/client/display/gl_canvas.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_CANVAS_H_
-#define REMOTING_CLIENT_OPENGL_GL_CANVAS_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_
+#define REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_
 
 #include <array>
 
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
-#include "remoting/client/sys_opengl.h"
+#include "remoting/client/display/sys_opengl.h"
 
 namespace remoting {
 
@@ -91,4 +91,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_OPENGL_GL_CANVAS_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_CANVAS_H_
diff --git a/remoting/client/gl_cursor.cc b/remoting/client/display/gl_cursor.cc
similarity index 90%
rename from remoting/client/gl_cursor.cc
rename to remoting/client/display/gl_cursor.cc
index 152923ad..4a4c55a 100644
--- a/remoting/client/gl_cursor.cc
+++ b/remoting/client/display/gl_cursor.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_cursor.h"
+#include "remoting/client/display/gl_cursor.h"
 
 #include "remoting/base/util.h"
-#include "remoting/client/gl_canvas.h"
-#include "remoting/client/gl_math.h"
-#include "remoting/client/gl_render_layer.h"
-#include "remoting/client/gl_texture_ids.h"
+#include "remoting/client/display/gl_canvas.h"
+#include "remoting/client/display/gl_math.h"
+#include "remoting/client/display/gl_render_layer.h"
+#include "remoting/client/display/gl_texture_ids.h"
 #include "remoting/proto/control.pb.h"
 #include "third_party/libyuv/include/libyuv/convert_argb.h"
 
@@ -24,7 +24,7 @@
 
 void GlCursor::SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) {
   int data_size = cursor_shape.width() * cursor_shape.height() *
-      GlRenderLayer::kBytesPerPixel;
+                  GlRenderLayer::kBytesPerPixel;
   if (current_cursor_data_size_ < data_size) {
     current_cursor_data_size_ =
         kDefaultCursorDataSize > data_size ? kDefaultCursorDataSize : data_size;
diff --git a/remoting/client/gl_cursor.h b/remoting/client/display/gl_cursor.h
similarity index 91%
rename from remoting/client/gl_cursor.h
rename to remoting/client/display/gl_cursor.h
index 365b2c2..eec03e8 100644
--- a/remoting/client/gl_cursor.h
+++ b/remoting/client/display/gl_cursor.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_CURSOR_H_
-#define REMOTING_CLIENT_OPENGL_GL_CURSOR_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_
+#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_
 
 #include <array>
 #include <cstdint>
@@ -63,4 +63,4 @@
 };
 
 }  // namespace remoting
-#endif  // REMOTING_CLIENT_OPENGL_GL_CURSOR_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_CURSOR_H_
diff --git a/remoting/client/gl_cursor_feedback.cc b/remoting/client/display/gl_cursor_feedback.cc
similarity index 85%
rename from remoting/client/gl_cursor_feedback.cc
rename to remoting/client/display/gl_cursor_feedback.cc
index c3fcbb49..41736aed 100644
--- a/remoting/client/gl_cursor_feedback.cc
+++ b/remoting/client/display/gl_cursor_feedback.cc
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_cursor_feedback.h"
+#include "remoting/client/display/gl_cursor_feedback.h"
 
 #include <math.h>
 
 #include <array>
 
 #include "base/logging.h"
-#include "remoting/client/gl_canvas.h"
-#include "remoting/client/gl_cursor_feedback_texture.h"
-#include "remoting/client/gl_math.h"
-#include "remoting/client/gl_render_layer.h"
-#include "remoting/client/gl_texture_ids.h"
+#include "remoting/client/display/gl_canvas.h"
+#include "remoting/client/display/gl_cursor_feedback_texture.h"
+#include "remoting/client/display/gl_math.h"
+#include "remoting/client/display/gl_render_layer.h"
+#include "remoting/client/display/gl_texture_ids.h"
 
 namespace {
 
@@ -80,8 +80,8 @@
   float diameter = GetExpansionCoefficient(progress) * max_diameter_;
   std::array<float, 8> positions;
   FillRectangleVertexPositions(cursor_x_ - diameter / 2,
-                               cursor_y_ - diameter / 2,
-                               diameter, diameter, &positions);
+                               cursor_y_ - diameter / 2, diameter, diameter,
+                               &positions);
   layer_->SetVertexPositions(positions);
 
   // Linear fade-out.
diff --git a/remoting/client/gl_cursor_feedback.h b/remoting/client/display/gl_cursor_feedback.h
similarity index 86%
rename from remoting/client/gl_cursor_feedback.h
rename to remoting/client/display/gl_cursor_feedback.h
index 6405efc..230b523 100644
--- a/remoting/client/gl_cursor_feedback.h
+++ b/remoting/client/display/gl_cursor_feedback.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_CURSOR_FEEDBACK_H_
-#define REMOTING_CLIENT_OPENGL_GL_CURSOR_FEEDBACK_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_
+#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_
 
 #include <stdint.h>
 
@@ -45,4 +45,4 @@
 };
 
 }  // namespace remoting
-#endif  // REMOTING_CLIENT_OPENGL_GL_CURSOR_FEEDBACK_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_H_
diff --git a/remoting/client/gl_cursor_feedback_texture.cc b/remoting/client/display/gl_cursor_feedback_texture.cc
similarity index 89%
rename from remoting/client/gl_cursor_feedback_texture.cc
rename to remoting/client/display/gl_cursor_feedback_texture.cc
index d41d222..33a839e 100644
--- a/remoting/client/gl_cursor_feedback_texture.cc
+++ b/remoting/client/display/gl_cursor_feedback_texture.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_cursor_feedback_texture.h"
+#include "remoting/client/display/gl_cursor_feedback_texture.h"
 
-#include "remoting/client/gl_render_layer.h"
+#include "remoting/client/display/gl_render_layer.h"
 
 namespace remoting {
 
@@ -17,15 +17,15 @@
 // RGBA8888 colors. From inside to outside.
 const uint8_t kFeedbackRingColors[kColorRingsCount]
                                  [GlRenderLayer::kBytesPerPixel] = {
-                                     {0x0, 0x0, 0x0, 0x7f},  // Black
-                                     {0x0, 0x0, 0x0, 0x7f},  // Black
+                                     {0x0, 0x0, 0x0, 0x7f},     // Black
+                                     {0x0, 0x0, 0x0, 0x7f},     // Black
                                      {0xff, 0xff, 0xff, 0x7f},  // White
                                      {0xff, 0xff, 0xff, 0x7f},  // White
                                      {0xff, 0xff, 0xff, 0}  // Transparent White
 };
 
-const float kFeedbackRadiusStops[kColorRingsCount] =
-    {0.0f, 0.85f, 0.9f, 0.95f, 1.0f};
+const float kFeedbackRadiusStops[kColorRingsCount] = {0.0f, 0.85f, 0.9f, 0.95f,
+                                                      1.0f};
 
 uint32_t GetColorByRadius(float radius) {
   int ring_index = kColorRingsCount - 1;
diff --git a/remoting/client/gl_cursor_feedback_texture.h b/remoting/client/display/gl_cursor_feedback_texture.h
similarity index 80%
rename from remoting/client/gl_cursor_feedback_texture.h
rename to remoting/client/display/gl_cursor_feedback_texture.h
index 1826d21..c73dc035 100644
--- a/remoting/client/gl_cursor_feedback_texture.h
+++ b/remoting/client/display/gl_cursor_feedback_texture.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_GL_CURSOR_FEEDBACK_TEXTURE_H_
-#define REMOTING_CLIENT_GL_CURSOR_FEEDBACK_TEXTURE_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_
+#define REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_
 
 #include <vector>
 
@@ -34,4 +34,4 @@
 };
 
 }  // namespace remoting
-#endif  // REMOTING_CLIENT_GL_CURSOR_FEEDBACK_TEXTURE_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_CURSOR_FEEDBACK_TEXTURE_H_
diff --git a/remoting/client/gl_desktop.cc b/remoting/client/display/gl_desktop.cc
similarity index 94%
rename from remoting/client/gl_desktop.cc
rename to remoting/client/display/gl_desktop.cc
index 72bd9ec..e033d54 100644
--- a/remoting/client/gl_desktop.cc
+++ b/remoting/client/display/gl_desktop.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_desktop.h"
+#include "remoting/client/display/gl_desktop.h"
 
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "remoting/client/gl_canvas.h"
-#include "remoting/client/gl_math.h"
-#include "remoting/client/gl_render_layer.h"
-#include "remoting/client/gl_texture_ids.h"
+#include "remoting/client/display/gl_canvas.h"
+#include "remoting/client/display/gl_math.h"
+#include "remoting/client/display/gl_render_layer.h"
+#include "remoting/client/display/gl_texture_ids.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
 
diff --git a/remoting/client/gl_desktop.h b/remoting/client/display/gl_desktop.h
similarity index 89%
rename from remoting/client/gl_desktop.h
rename to remoting/client/display/gl_desktop.h
index 143a063..d43247c5 100644
--- a/remoting/client/gl_desktop.h
+++ b/remoting/client/display/gl_desktop.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_DESKTOP_H_
-#define REMOTING_CLIENT_OPENGL_GL_DESKTOP_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_
+#define REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_
 
 #include <memory>
 #include <vector>
@@ -52,4 +52,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_OPENGL_GL_DESKTOP_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_DESKTOP_H_
diff --git a/remoting/client/gl_helpers.cc b/remoting/client/display/gl_helpers.cc
similarity index 97%
rename from remoting/client/gl_helpers.cc
rename to remoting/client/display/gl_helpers.cc
index 5cd1863..bce438b 100644
--- a/remoting/client/gl_helpers.cc
+++ b/remoting/client/display/gl_helpers.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 "remoting/client/gl_helpers.h"
+#include "remoting/client/display/gl_helpers.h"
 
 #include "base/logging.h"
 
diff --git a/remoting/client/gl_helpers.h b/remoting/client/display/gl_helpers.h
similarity index 81%
rename from remoting/client/gl_helpers.h
rename to remoting/client/display/gl_helpers.h
index 8dba721..16218d9 100644
--- a/remoting/client/gl_helpers.h
+++ b/remoting/client/display/gl_helpers.h
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_GL_HELPERS_H_
-#define REMOTING_CLIENT_GL_HELPERS_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_
+#define REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_
 
 #include "base/macros.h"
-#include "remoting/client/sys_opengl.h"
+#include "remoting/client/display/sys_opengl.h"
 
 namespace remoting {
 
@@ -26,4 +26,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_GL_HELPERS_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_HELPERS_H_
diff --git a/remoting/client/gl_math.cc b/remoting/client/display/gl_math.cc
similarity index 96%
rename from remoting/client/gl_math.cc
rename to remoting/client/display/gl_math.cc
index 6102e3c..b91229b 100644
--- a/remoting/client/gl_math.cc
+++ b/remoting/client/display/gl_math.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 "remoting/client/gl_math.h"
+#include "remoting/client/display/gl_math.h"
 
 #include <sstream>
 
diff --git a/remoting/client/gl_math.h b/remoting/client/display/gl_math.h
similarity index 90%
rename from remoting/client/gl_math.h
rename to remoting/client/display/gl_math.h
index cc8fc1b..184d37c 100644
--- a/remoting/client/gl_math.h
+++ b/remoting/client/display/gl_math.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_MATH_H_
-#define REMOTING_CLIENT_OPENGL_GL_MATH_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_MATH_H_
+#define REMOTING_CLIENT_DISPLAY_GL_MATH_H_
 
 #include <array>
 #include <string>
@@ -43,4 +43,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_OPENGL_GL_MATH_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_MATH_H_
diff --git a/remoting/client/gl_render_layer.cc b/remoting/client/display/gl_render_layer.cc
similarity index 97%
rename from remoting/client/gl_render_layer.cc
rename to remoting/client/display/gl_render_layer.cc
index 7d3e06ce..6fcb08c 100644
--- a/remoting/client/gl_render_layer.cc
+++ b/remoting/client/display/gl_render_layer.cc
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_render_layer.h"
+#include "remoting/client/display/gl_render_layer.h"
 
 #include "base/logging.h"
-#include "remoting/client/gl_canvas.h"
-#include "remoting/client/gl_helpers.h"
-
+#include "remoting/client/display/gl_canvas.h"
+#include "remoting/client/display/gl_helpers.h"
 
 namespace remoting {
 
diff --git a/remoting/client/gl_render_layer.h b/remoting/client/display/gl_render_layer.h
similarity index 94%
rename from remoting/client/gl_render_layer.h
rename to remoting/client/display/gl_render_layer.h
index 5306e84..153e2e3e 100644
--- a/remoting/client/gl_render_layer.h
+++ b/remoting/client/display/gl_render_layer.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_OPENGL_GL_RENDER_LAYER_H_
-#define REMOTING_CLIENT_OPENGL_GL_RENDER_LAYER_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_
+#define REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_
 
 #include <array>
 #include <memory>
 
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
-#include "remoting/client/sys_opengl.h"
+#include "remoting/client/display/sys_opengl.h"
 
 namespace remoting {
 class GlCanvas;
@@ -97,4 +97,4 @@
 };
 
 }  // namespace remoting
-#endif  // REMOTING_CLIENT_OPENGL_GL_RENDER_LAYER_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_RENDER_LAYER_H_
diff --git a/remoting/client/gl_renderer.cc b/remoting/client/display/gl_renderer.cc
similarity index 94%
rename from remoting/client/gl_renderer.cc
rename to remoting/client/display/gl_renderer.cc
index 64ce7d3..c975e54 100644
--- a/remoting/client/gl_renderer.cc
+++ b/remoting/client/display/gl_renderer.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "remoting/client/gl_renderer.h"
+#include "remoting/client/display/gl_renderer.h"
 
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "remoting/client/gl_canvas.h"
-#include "remoting/client/gl_math.h"
-#include "remoting/client/gl_renderer_delegate.h"
-#include "remoting/client/sys_opengl.h"
+#include "remoting/client/display/gl_canvas.h"
+#include "remoting/client/display/gl_math.h"
+#include "remoting/client/display/gl_renderer_delegate.h"
+#include "remoting/client/display/sys_opengl.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
 
 namespace remoting {
diff --git a/remoting/client/gl_renderer.h b/remoting/client/display/gl_renderer.h
similarity index 93%
rename from remoting/client/gl_renderer.h
rename to remoting/client/display/gl_renderer.h
index 832bd63..3cecb13 100644
--- a/remoting/client/gl_renderer.h
+++ b/remoting/client/display/gl_renderer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_GL_RENDERER_H_
-#define REMOTING_CLIENT_GL_RENDERER_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_
+#define REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_
 
 #include <queue>
 
@@ -11,9 +11,9 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
-#include "remoting/client/gl_cursor.h"
-#include "remoting/client/gl_cursor_feedback.h"
-#include "remoting/client/gl_desktop.h"
+#include "remoting/client/display/gl_cursor.h"
+#include "remoting/client/display/gl_cursor_feedback.h"
+#include "remoting/client/display/gl_desktop.h"
 #include "remoting/proto/control.pb.h"
 
 namespace webrtc {
@@ -134,4 +134,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_GL_RENDERER_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_RENDERER_H_
diff --git a/remoting/client/gl_renderer_delegate.h b/remoting/client/display/gl_renderer_delegate.h
similarity index 83%
rename from remoting/client/gl_renderer_delegate.h
rename to remoting/client/display/gl_renderer_delegate.h
index 5d15a1e7..af97759 100644
--- a/remoting/client/gl_renderer_delegate.h
+++ b/remoting/client/display/gl_renderer_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_GL_RENDERER_DELEGATE_H_
-#define REMOTING_CLIENT_GL_RENDERER_DELEGATE_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_
+#define REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_
 
 #include "base/macros.h"
 
@@ -29,4 +29,4 @@
 };
 
 }  // namespace remoting
-#endif  // REMOTING_CLIENT_GL_RENDERER_DELEGATE_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_RENDERER_DELEGATE_H_
diff --git a/remoting/client/gl_renderer_unittest.cc b/remoting/client/display/gl_renderer_unittest.cc
similarity index 90%
rename from remoting/client/gl_renderer_unittest.cc
rename to remoting/client/display/gl_renderer_unittest.cc
index 1a17e42..7edb9bd0 100644
--- a/remoting/client/gl_renderer_unittest.cc
+++ b/remoting/client/display/gl_renderer_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 "remoting/client/gl_renderer.h"
+#include "remoting/client/display/gl_renderer.h"
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -10,7 +10,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "remoting/client/gl_renderer_delegate.h"
+#include "remoting/client/display/gl_renderer_delegate.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
 
@@ -42,29 +42,19 @@
     on_frame_rendered_callback_ = callback;
   }
 
-  int canvas_width() {
-    return canvas_width_;
-  }
+  int canvas_width() { return canvas_width_; }
 
-  int canvas_height() {
-    return canvas_height_;
-  }
+  int canvas_height() { return canvas_height_; }
 
   base::WeakPtr<FakeGlRendererDelegate> GetWeakPtr() {
     return weak_factory_.GetWeakPtr();
   }
 
-  int can_render_frame_call_count() {
-    return can_render_frame_call_count_;
-  }
+  int can_render_frame_call_count() { return can_render_frame_call_count_; }
 
-  int on_frame_rendered_call_count() {
-    return on_frame_rendered_call_count_;
-  }
+  int on_frame_rendered_call_count() { return on_frame_rendered_call_count_; }
 
-  int on_size_changed_call_count() {
-    return on_size_changed_call_count_;
-  }
+  int on_size_changed_call_count() { return on_size_changed_call_count_; }
 
   bool can_render_frame_ = false;
 
@@ -120,8 +110,8 @@
                  base::Unretained(this)));
 }
 
-void GlRendererTest::PostSetDesktopFrameTasks(
-    const webrtc::DesktopSize& size, int count) {
+void GlRendererTest::PostSetDesktopFrameTasks(const webrtc::DesktopSize& size,
+                                              int count) {
   for (int i = 0; i < count; i++) {
     message_loop_.task_runner()->PostTask(
         FROM_HERE, base::Bind(&GlRendererTest::SetDesktopFrameWithSize,
@@ -145,7 +135,6 @@
   run_loop.Run();
 }
 
-
 TEST_F(GlRendererTest, TestDelegateCanRenderFrame) {
   delegate_.can_render_frame_ = true;
   RequestRender();
diff --git a/remoting/client/gl_texture_ids.h b/remoting/client/display/gl_texture_ids.h
similarity index 77%
rename from remoting/client/gl_texture_ids.h
rename to remoting/client/display/gl_texture_ids.h
index a4fb91e..3680973 100644
--- a/remoting/client/gl_texture_ids.h
+++ b/remoting/client/display/gl_texture_ids.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_GL_TEXTURE_IDS_H_
-#define REMOTING_CLIENT_GL_TEXTURE_IDS_H_
+#ifndef REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_
+#define REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_
 
 #include "base/macros.h"
 
@@ -19,4 +19,4 @@
 
 }  // namespace remoting
 
-#endif  // REMOTING_CLIENT_GL_TEXTURE_IDS_H_
+#endif  // REMOTING_CLIENT_DISPLAY_GL_TEXTURE_IDS_H_
diff --git a/remoting/client/sys_opengl.h b/remoting/client/display/sys_opengl.h
similarity index 77%
rename from remoting/client/sys_opengl.h
rename to remoting/client/display/sys_opengl.h
index 8a1c907d..90abd6be 100644
--- a/remoting/client/sys_opengl.h
+++ b/remoting/client/display/sys_opengl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef REMOTING_CLIENT_SYS_OPENGL_H_
-#define REMOTING_CLIENT_SYS_OPENGL_H_
+#ifndef REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_
+#define REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_
 
 #include "build/build_config.h"
 
@@ -21,4 +21,4 @@
 #include <GLES3/gl3.h>
 #endif  // defined(OS_IOS)
 
-#endif  // REMOTING_CLIENT_SYS_OPENGL_H_
+#endif  // REMOTING_CLIENT_DISPLAY_SYS_OPENGL_H_
diff --git a/remoting/client/ios/example_view_controller.mm b/remoting/client/ios/example_view_controller.mm
index dbac4297..64fd9ac 100644
--- a/remoting/client/ios/example_view_controller.mm
+++ b/remoting/client/ios/example_view_controller.mm
@@ -8,7 +8,7 @@
 
 #import "remoting/client/ios/example_view_controller.h"
 
-#import "remoting/client/sys_opengl.h"
+#import "remoting/client/display/sys_opengl.h"
 
 @interface ExampleViewController()
 
diff --git a/remoting/client/jni/jni_gl_display_handler.h b/remoting/client/jni/jni_gl_display_handler.h
index df79553..12a5d78 100644
--- a/remoting/client/jni/jni_gl_display_handler.h
+++ b/remoting/client/jni/jni_gl_display_handler.h
@@ -11,8 +11,8 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "remoting/client/gl_renderer.h"
-#include "remoting/client/gl_renderer_delegate.h"
+#include "remoting/client/display/gl_renderer.h"
+#include "remoting/client/display/gl_renderer_delegate.h"
 #include "remoting/client/queued_task_poster.h"
 #include "remoting/protocol/cursor_shape_stub.h"
 
diff --git a/services/ui/surfaces/display_compositor.cc b/services/ui/surfaces/display_compositor.cc
index 80faca0..35c92cd 100644
--- a/services/ui/surfaces/display_compositor.cc
+++ b/services/ui/surfaces/display_compositor.cc
@@ -169,13 +169,13 @@
   }
 
   // Remove markers for temporary references up to |child_id|, as the temporary
-  // references they correspond to were removed above. If |ref_iter| is the last
-  // element in |refs| then we are removing all temporary references for the
-  // FrameSinkId and can remove the map entry entirely.
+  // references they correspond to were removed above. If |temp_ref_iter| points
+  // at the last element in |refs| then we are removing all temporary references
+  // for the FrameSinkId and can remove the map entry entirely.
   if (++temp_ref_iter == refs.end())
     temp_references_.erase(child_id.frame_sink_id());
   else
-    refs.erase(refs.begin(), ++temp_ref_iter);
+    refs.erase(refs.begin(), temp_ref_iter);
 }
 
 void DisplayCompositor::RemoveSurfaceReference(
diff --git a/services/ui/surfaces/display_compositor_unittest.cc b/services/ui/surfaces/display_compositor_unittest.cc
index 23d32a5..6d2cf5a1 100644
--- a/services/ui/surfaces/display_compositor_unittest.cc
+++ b/services/ui/surfaces/display_compositor_unittest.cc
@@ -285,5 +285,36 @@
   EXPECT_EQ(0u, CountTempReferences());
 }
 
+TEST_F(DisplayCompositorTest, RemoveFirstTempRefOnly) {
+  const cc::SurfaceId parent_id = MakeSurfaceId(1, 1, 1);
+  const cc::SurfaceId surface_id1 = MakeSurfaceId(2, 1, 1);
+  const cc::SurfaceId surface_id2 = MakeSurfaceId(2, 1, 2);
+
+  // Add two surfaces that have the same FrameSinkId. This would happen when a
+  // client submits two CFs before parent submits a new CF.
+  surface_observer()->OnSurfaceCreated(
+      cc::SurfaceInfo(surface_id1, 1.0f, gfx::Size(1, 1)));
+  surface_observer()->OnSurfaceCreated(
+      cc::SurfaceInfo(surface_id2, 1.0f, gfx::Size(1, 1)));
+  RunUntilIdle();
+
+  // Client should get OnSurfaceCreated call and temporary reference added for
+  // both surfaces.
+  EXPECT_EQ("OnSurfaceCreated(2:1:1);OnSurfaceCreated(2:1:2)",
+            client_.events());
+  EXPECT_EQ("Add(0:0:0-2:1:1);Add(0:0:0-2:1:2)", reference_manager_.events());
+  EXPECT_EQ(2u, CountTempReferences());
+
+  // Add a reference to the surface with the earlier LocalFrameId.
+  AddSurfaceReference(parent_id, surface_id1);
+  RunUntilIdle();
+
+  // The real reference should be added for 2:1:1 and temporary reference
+  // should be removed. The temporary reference for 2:1:2 should remain.
+  EXPECT_EQ("Add(1:1:1-2:1:1);Remove(0:0:0-2:1:1)",
+            reference_manager_.events());
+  EXPECT_EQ(1u, CountTempReferences());
+}
+
 }  // namespace test
 }  // namespace ui
diff --git a/services/ui/ws/display.cc b/services/ui/ws/display.cc
index 6314eb3..8a9cf98 100644
--- a/services/ui/ws/display.cc
+++ b/services/ui/ws/display.cc
@@ -271,6 +271,13 @@
   return root_.get();
 }
 
+ServerWindow* Display::GetActiveRootWindow() {
+  WindowManagerDisplayRoot* display_root = GetActiveWindowManagerDisplayRoot();
+  if (display_root)
+    return display_root->root();
+  return nullptr;
+}
+
 void Display::OnAcceleratedWidgetAvailable() {
   display_manager()->OnDisplayAcceleratedWidgetAvailable(this);
   InitWindowManagerDisplayRoots();
diff --git a/services/ui/ws/display.h b/services/ui/ws/display.h
index 839f5e94..28bf83b 100644
--- a/services/ui/ws/display.h
+++ b/services/ui/ws/display.h
@@ -166,6 +166,7 @@
   // PlatformDisplayDelegate:
   display::Display GetDisplay() override;
   ServerWindow* GetRootWindow() override;
+  ServerWindow* GetActiveRootWindow() override;
   void OnAcceleratedWidgetAvailable() override;
   bool IsInHighContrastMode() override;
   void OnEvent(const ui::Event& event) override;
diff --git a/services/ui/ws/frame_generator.cc b/services/ui/ws/frame_generator.cc
index 7229a29..c4c309b 100644
--- a/services/ui/ws/frame_generator.cc
+++ b/services/ui/ws/frame_generator.cc
@@ -130,7 +130,7 @@
   render_pass->SetNew(render_pass_id, output_rect, output_rect,
                       gfx::Transform());
 
-  DrawWindowTree(render_pass.get(), root_window_, gfx::Vector2d(), 1.0f);
+  DrawWindow(render_pass.get(), delegate_->GetActiveRootWindow());
 
   cc::CompositorFrame frame;
   frame.render_pass_list.push_back(std::move(render_pass));
@@ -155,80 +155,40 @@
   return frame;
 }
 
-void FrameGenerator::DrawWindowTree(
-    cc::RenderPass* pass,
-    ServerWindow* window,
-    const gfx::Vector2d& parent_to_root_origin_offset,
-    float opacity) {
-  if (!window->visible())
+void FrameGenerator::DrawWindow(cc::RenderPass* pass, ServerWindow* window) {
+  if (!window || !window->visible())
     return;
 
-  const gfx::Rect absolute_bounds =
-      window->bounds() + parent_to_root_origin_offset;
-  const ServerWindow::Windows& children = window->children();
-  const float combined_opacity = opacity * window->opacity();
-  for (ServerWindow* child : base::Reversed(children)) {
-    DrawWindowTree(pass, child, absolute_bounds.OffsetFromOrigin(),
-                   combined_opacity);
-  }
-
   if (!window->compositor_frame_sink_manager() ||
       !window->compositor_frame_sink_manager()->ShouldDraw())
     return;
 
-  cc::SurfaceId underlay_surface_id =
-      window->compositor_frame_sink_manager()->GetLatestSurfaceId(
-          mojom::CompositorFrameSinkType::UNDERLAY);
   cc::SurfaceId default_surface_id =
       window->compositor_frame_sink_manager()->GetLatestSurfaceId(
           mojom::CompositorFrameSinkType::DEFAULT);
 
-  if (!underlay_surface_id.is_valid() && !default_surface_id.is_valid())
+  if (!default_surface_id.is_valid())
     return;
 
-  if (default_surface_id.is_valid()) {
-    gfx::Transform quad_to_target_transform;
-    quad_to_target_transform.Translate(absolute_bounds.x(),
-                                       absolute_bounds.y());
+  gfx::Transform quad_to_target_transform;
+  quad_to_target_transform.Translate(window->bounds().x(),
+                                     window->bounds().y());
 
-    cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
+  cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
 
-    const gfx::Rect bounds_at_origin(window->bounds().size());
-    // TODO(fsamuel): These clipping and visible rects are incorrect. They need
-    // to be populated from CompositorFrame structs.
-    sqs->SetAll(
-        quad_to_target_transform, bounds_at_origin.size() /* layer_bounds */,
-        bounds_at_origin /* visible_layer_bounds */,
-        bounds_at_origin /* clip_rect */, false /* is_clipped */,
-        combined_opacity, SkBlendMode::kSrcOver, 0 /* sorting-context_id */);
-    auto* quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
-    quad->SetAll(sqs, bounds_at_origin /* rect */,
-                 gfx::Rect() /* opaque_rect */,
-                 bounds_at_origin /* visible_rect */, true /* needs_blending*/,
-                 default_surface_id);
-  }
-  if (underlay_surface_id.is_valid()) {
-    const gfx::Rect underlay_absolute_bounds =
-        absolute_bounds - window->underlay_offset();
-    gfx::Transform quad_to_target_transform;
-    quad_to_target_transform.Translate(underlay_absolute_bounds.x(),
-                                       underlay_absolute_bounds.y());
-    cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
-    const gfx::Rect bounds_at_origin(
-        window->compositor_frame_sink_manager()->GetLatestFrameSize(
-            mojom::CompositorFrameSinkType::UNDERLAY));
-    sqs->SetAll(
-        quad_to_target_transform, bounds_at_origin.size() /* layer_bounds */,
-        bounds_at_origin /* visible_layer_bounds */,
-        bounds_at_origin /* clip_rect */, false /* is_clipped */,
-        combined_opacity, SkBlendMode::kSrcOver, 0 /* sorting-context_id */);
-
-    auto* quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
-    quad->SetAll(sqs, bounds_at_origin /* rect */,
-                 gfx::Rect() /* opaque_rect */,
-                 bounds_at_origin /* visible_rect */, true /* needs_blending*/,
-                 underlay_surface_id);
-  }
+  const gfx::Rect bounds_at_origin(window->bounds().size());
+  // TODO(fsamuel): These clipping and visible rects are incorrect. They need
+  // to be populated from CompositorFrame structs.
+  sqs->SetAll(
+      quad_to_target_transform, bounds_at_origin.size() /* layer_bounds */,
+      bounds_at_origin /* visible_layer_bounds */,
+      bounds_at_origin /* clip_rect */, false /* is_clipped */,
+      1.0f /* opacity */, SkBlendMode::kSrcOver, 0 /* sorting-context_id */);
+  auto* quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+  quad->SetAll(sqs, bounds_at_origin /* rect */,
+               gfx::Rect() /* opaque_rect */,
+               bounds_at_origin /* visible_rect */, true /* needs_blending*/,
+               default_surface_id);
 }
 
 cc::SurfaceId FrameGenerator::FindParentSurfaceId(ServerWindow* window) {
diff --git a/services/ui/ws/frame_generator.h b/services/ui/ws/frame_generator.h
index 706f851..d60b8b2 100644
--- a/services/ui/ws/frame_generator.h
+++ b/services/ui/ws/frame_generator.h
@@ -80,12 +80,9 @@
   // Generates the CompositorFrame.
   cc::CompositorFrame GenerateCompositorFrame(const gfx::Rect& output_rect);
 
-  // DrawWindowTree recursively visits ServerWindows, creating a SurfaceDrawQuad
-  // for each that lacks one.
-  void DrawWindowTree(cc::RenderPass* pass,
-                      ServerWindow* window,
-                      const gfx::Vector2d& parent_to_root_origin_offset,
-                      float opacity);
+  // DrawWindow creates SurfaceDrawQuad for the provided ServerWindow and
+  // appends it to the provided cc::RenderPass.
+  void DrawWindow(cc::RenderPass* pass, ServerWindow* window);
 
   // Finds the parent surface id for |window|.
   cc::SurfaceId FindParentSurfaceId(ServerWindow* window);
diff --git a/services/ui/ws/frame_generator_delegate.h b/services/ui/ws/frame_generator_delegate.h
index 03fbcd04..88b2085 100644
--- a/services/ui/ws/frame_generator_delegate.h
+++ b/services/ui/ws/frame_generator_delegate.h
@@ -10,6 +10,8 @@
 
 class FrameGeneratorDelegate {
  public:
+  // Returns the root window of the active user.
+  virtual ServerWindow* GetActiveRootWindow() = 0;
   virtual bool IsInHighContrastMode() = 0;
 
  protected:
diff --git a/services/ui/ws/frame_generator_unittest.cc b/services/ui/ws/frame_generator_unittest.cc
index 49c4a98..ea6017e 100644
--- a/services/ui/ws/frame_generator_unittest.cc
+++ b/services/ui/ws/frame_generator_unittest.cc
@@ -52,8 +52,8 @@
                                                     kRootDisplayId)) {}
   ~FrameGeneratorTest() override {}
 
-  // Calls DrawWindowTree() on |frame_generator_|
-  void DrawWindowTree(cc::RenderPass* pass);
+  // Calls DrawWindow() on |frame_generator_|
+  void DrawWindow(cc::RenderPass* pass);
 
   ServerWindow* root_window() { return root_window_.get(); }
 
@@ -75,14 +75,14 @@
   DISALLOW_COPY_AND_ASSIGN(FrameGeneratorTest);
 };
 
-void FrameGeneratorTest::DrawWindowTree(cc::RenderPass* pass) {
-  frame_generator_->DrawWindowTree(pass, root_window_.get(), gfx::Vector2d(),
-                                   1.0f);
+void FrameGeneratorTest::DrawWindow(cc::RenderPass* pass) {
+  frame_generator_->DrawWindow(pass, root_window_.get());
 }
 
 void FrameGeneratorTest::SetUp() {
   testing::Test::SetUp();
-  frame_generator_delegate_ = base::MakeUnique<TestFrameGeneratorDelegate>();
+  frame_generator_delegate_ =
+      base::MakeUnique<TestFrameGeneratorDelegate>(root_window_.get());
   PlatformDisplayInitParams init_params;
   frame_generator_ = base::MakeUnique<FrameGenerator>(
       frame_generator_delegate_.get(), root_window_.get());
@@ -97,51 +97,23 @@
 }
 
 // Tests correctness of the SharedQuadStateList generated by
-// FrameGenerator::DrawWindowTree().
-TEST_F(FrameGeneratorTest, DrawWindowTree) {
+// FrameGenerator::DrawWindow().
+TEST_F(FrameGeneratorTest, DrawWindow) {
   ServerWindow child_window(test_window_delegate(), WindowId(1, 1));
   root_window()->Add(&child_window);
   InitWindow(&child_window);
-  const float root_opacity = .5f;
   const float child_opacity = .4f;
-  root_window()->SetOpacity(root_opacity);
   child_window.SetOpacity(child_opacity);
 
   std::unique_ptr<cc::RenderPass> render_pass = cc::RenderPass::Create();
-  DrawWindowTree(render_pass.get());
+  DrawWindow(render_pass.get());
   cc::SharedQuadStateList* quad_state_list =
       &render_pass->shared_quad_state_list;
 
-  // Both child and root have a DEFAULT Surface and no underlay Surfaces, so
-  // there should be two SharedQuadStates in the list.
-  EXPECT_EQ(2u, quad_state_list->size());
+  EXPECT_EQ(1u, quad_state_list->size());
   cc::SharedQuadState* root_sqs = quad_state_list->back();
-  cc::SharedQuadState* child_sqs = quad_state_list->front();
-  EXPECT_EQ(root_opacity, root_sqs->opacity);
-  // Child's SharedQuadState contains the effective opacity of the child layer,
-  // which should be a product of the child and the parent opacity.
-  EXPECT_EQ(child_opacity * root_opacity, child_sqs->opacity);
-
-  // Pretend to create the UNDERLAY Surface for the child window, and confirm
-  // that this creates an extra SharedQuadState in the CompositorFrame.
-  child_window.GetOrCreateCompositorFrameSinkManager()->SetLatestSurfaceInfo(
-      mojom::CompositorFrameSinkType::UNDERLAY,
-      cc::SurfaceInfo(
-          cc::SurfaceId(
-              cc::FrameSinkId(WindowIdToTransportId(child_window.id()),
-                              static_cast<uint32_t>(
-                                  mojom::CompositorFrameSinkType::UNDERLAY)),
-              cc::LocalFrameId(1u, kArbitraryToken)),
-          1.0f, gfx::Size(100, 100)));
-
-  render_pass = cc::RenderPass::Create();
-  DrawWindowTree(render_pass.get());
-  quad_state_list = &render_pass->shared_quad_state_list;
-  EXPECT_EQ(3u, quad_state_list->size());
-  auto it = quad_state_list->begin();
-  EXPECT_EQ(child_opacity * root_opacity, (*it)->opacity);
-  EXPECT_EQ(child_opacity * root_opacity, (*++it)->opacity);
-  EXPECT_EQ(root_opacity, (*++it)->opacity);
+  // Opacity should always be 1.
+  EXPECT_EQ(1.0f, root_sqs->opacity);
 }
 
 }  // namespace test
diff --git a/services/ui/ws/platform_display_default.cc b/services/ui/ws/platform_display_default.cc
index 6a3a6e9..e69588ed 100644
--- a/services/ui/ws/platform_display_default.cc
+++ b/services/ui/ws/platform_display_default.cc
@@ -248,6 +248,10 @@
 
 void PlatformDisplayDefault::OnActivationChanged(bool active) {}
 
+ServerWindow* PlatformDisplayDefault::GetActiveRootWindow() {
+  return delegate_->GetActiveRootWindow();
+}
+
 bool PlatformDisplayDefault::IsInHighContrastMode() {
   return delegate_ ? delegate_->IsInHighContrastMode() : false;
 }
diff --git a/services/ui/ws/platform_display_default.h b/services/ui/ws/platform_display_default.h
index 12c07f2..8636c2b 100644
--- a/services/ui/ws/platform_display_default.h
+++ b/services/ui/ws/platform_display_default.h
@@ -68,6 +68,7 @@
   void OnActivationChanged(bool active) override;
 
   // FrameGeneratorDelegate:
+  ServerWindow* GetActiveRootWindow() override;
   bool IsInHighContrastMode() override;
 
   const int64_t display_id_;
diff --git a/services/ui/ws/platform_display_delegate.h b/services/ui/ws/platform_display_delegate.h
index 0358573..808717bb 100644
--- a/services/ui/ws/platform_display_delegate.h
+++ b/services/ui/ws/platform_display_delegate.h
@@ -28,6 +28,9 @@
   // Returns the root window of this display.
   virtual ServerWindow* GetRootWindow() = 0;
 
+  // Returns the root window of the active user.
+  virtual ServerWindow* GetActiveRootWindow() = 0;
+
   // Called once when the AcceleratedWidget is available for drawing.
   virtual void OnAcceleratedWidgetAvailable() = 0;
 
diff --git a/services/ui/ws/server_window_compositor_frame_sink_manager.cc b/services/ui/ws/server_window_compositor_frame_sink_manager.cc
index 7480c7f..b55d0a89 100644
--- a/services/ui/ws/server_window_compositor_frame_sink_manager.cc
+++ b/services/ui/ws/server_window_compositor_frame_sink_manager.cc
@@ -29,9 +29,7 @@
     return true;
 
   waiting_for_initial_frames_ = !IsCompositorFrameSinkReadyAndNonEmpty(
-                                    mojom::CompositorFrameSinkType::DEFAULT) ||
-                                !IsCompositorFrameSinkReadyAndNonEmpty(
-                                    mojom::CompositorFrameSinkType::UNDERLAY);
+      mojom::CompositorFrameSinkType::DEFAULT);
   return !waiting_for_initial_frames_;
 }
 
diff --git a/services/ui/ws/server_window_compositor_frame_sink_manager.h b/services/ui/ws/server_window_compositor_frame_sink_manager.h
index 8ca40a34..eb21737 100644
--- a/services/ui/ws/server_window_compositor_frame_sink_manager.h
+++ b/services/ui/ws/server_window_compositor_frame_sink_manager.h
@@ -109,6 +109,8 @@
 
   TypeToCompositorFrameSinkMap type_to_compositor_frame_sink_map_;
 
+  // TODO(mfomitchev): This is currently always false. Confirm if we still need
+  // this.
   // While true the window is not drawn. This is initially true if the window
   // has the property |kWaitForUnderlay_Property|. This is set to false once
   // the underlay and default surface have been set *and* their size is at
diff --git a/services/ui/ws/test_utils.cc b/services/ui/ws/test_utils.cc
index 947f089..5c50fae 100644
--- a/services/ui/ws/test_utils.cc
+++ b/services/ui/ws/test_utils.cc
@@ -151,10 +151,16 @@
 
 // TestFrameGeneratorDelegate -------------------------------------------------
 
-TestFrameGeneratorDelegate::TestFrameGeneratorDelegate() {}
+TestFrameGeneratorDelegate::TestFrameGeneratorDelegate(
+    ServerWindow* root_window)
+    : root_window_(root_window) {}
 
 TestFrameGeneratorDelegate::~TestFrameGeneratorDelegate() {}
 
+ServerWindow* TestFrameGeneratorDelegate::GetActiveRootWindow() {
+  return root_window_;
+}
+
 bool TestFrameGeneratorDelegate::IsInHighContrastMode() {
   return false;
 }
diff --git a/services/ui/ws/test_utils.h b/services/ui/ws/test_utils.h
index 5f5cb30..808f16f1 100644
--- a/services/ui/ws/test_utils.h
+++ b/services/ui/ws/test_utils.h
@@ -285,13 +285,16 @@
 // A stub implementation of FrameGeneratorDelegate.
 class TestFrameGeneratorDelegate : public FrameGeneratorDelegate {
  public:
-  TestFrameGeneratorDelegate();
+  TestFrameGeneratorDelegate(ServerWindow* root_window);
   ~TestFrameGeneratorDelegate() override;
 
   // FrameGeneratorDelegate:
+  ServerWindow* GetActiveRootWindow() override;
   bool IsInHighContrastMode() override;
 
  private:
+  ServerWindow* root_window_;
+
   DISALLOW_COPY_AND_ASSIGN(TestFrameGeneratorDelegate);
 };
 
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 64bc750..db9b5423 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -367,10 +367,6 @@
   # Select the right BitmapPlatformDevice.
   if (is_win) {
     sources += [ "ext/bitmap_platform_device_win.cc" ]
-  } else if (is_mac) {
-    sources += [ "ext/bitmap_platform_device_mac.cc" ]
-  } else if (use_cairo) {
-    sources += [ "ext/bitmap_platform_device_cairo.cc" ]
   } else if (!is_ios) {
     sources += [ "ext/bitmap_platform_device_skia.cc" ]
   }
diff --git a/skia/ext/bitmap_platform_device.h b/skia/ext/bitmap_platform_device.h
deleted file mode 100644
index 1ca5f7e..0000000
--- a/skia/ext/bitmap_platform_device.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2012 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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_
-#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_
-
-// This file provides an easy way to include the appropriate
-// BitmapPlatformDevice header file for your platform.
-
-#include <stdint.h>
-
-#if defined(WIN32)
-#include "skia/ext/bitmap_platform_device_win.h"
-#elif defined(__APPLE__)
-#include "skia/ext/bitmap_platform_device_mac.h"
-#elif defined(USE_CAIRO)
-#include "skia/ext/bitmap_platform_device_cairo.h"
-#else
-#include "skia/ext/bitmap_platform_device_skia.h"
-#endif
-
-#endif  // SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_
diff --git a/skia/ext/bitmap_platform_device_cairo.cc b/skia/ext/bitmap_platform_device_cairo.cc
deleted file mode 100644
index 261228b..0000000
--- a/skia/ext/bitmap_platform_device_cairo.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2013 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 "build/build_config.h"
-#include "skia/ext/bitmap_platform_device_cairo.h"
-#include "skia/ext/platform_canvas.h"
-
-#if defined(OS_OPENBSD)
-#include <cairo.h>
-#else
-#include <cairo/cairo.h>
-#endif
-
-namespace skia {
-
-namespace {
-
-void CairoSurfaceReleaseProc(void*, void* context) {
-  SkASSERT(context);
-  cairo_surface_destroy(static_cast<cairo_surface_t*>(context));
-}
-
-// Back the destination bitmap by a Cairo surface.  The bitmap's
-// pixelRef takes ownership of the passed-in surface and will call
-// cairo_surface_destroy() upon destruction.
-//
-// Note: it may immediately destroy the surface, if it fails to create a bitmap
-// with pixels, thus the caller must either ref() the surface before hand, or
-// it must not refer to the surface after this call.
-bool InstallCairoSurfacePixels(SkBitmap* dst,
-                               cairo_surface_t* surface,
-                               bool is_opaque) {
-  SkASSERT(dst);
-  if (!surface) {
-    return false;
-  }
-  SkImageInfo info
-      = SkImageInfo::MakeN32(cairo_image_surface_get_width(surface),
-                             cairo_image_surface_get_height(surface),
-                             is_opaque ? kOpaque_SkAlphaType
-                                       : kPremul_SkAlphaType);
-  return dst->installPixels(info,
-                            cairo_image_surface_get_data(surface),
-                            cairo_image_surface_get_stride(surface),
-                            NULL,
-                            &CairoSurfaceReleaseProc,
-                            static_cast<void*>(surface));
-}
-
-void LoadMatrixToContext(cairo_t* context, const SkMatrix& matrix) {
-  cairo_matrix_t cairo_matrix;
-  cairo_matrix_init(&cairo_matrix,
-                    SkScalarToFloat(matrix.getScaleX()),
-                    SkScalarToFloat(matrix.getSkewY()),
-                    SkScalarToFloat(matrix.getSkewX()),
-                    SkScalarToFloat(matrix.getScaleY()),
-                    SkScalarToFloat(matrix.getTranslateX()),
-                    SkScalarToFloat(matrix.getTranslateY()));
-  cairo_set_matrix(context, &cairo_matrix);
-}
-
-void LoadClipToContext(cairo_t* context, const SkIRect& clip_bounds) {
-  cairo_reset_clip(context);
-
-  cairo_rectangle(context, clip_bounds.fLeft, clip_bounds.fTop,
-                  clip_bounds.width(), clip_bounds.height());
-  cairo_clip(context);
-}
-
-}  // namespace
-
-void BitmapPlatformDevice::LoadConfig(const SkMatrix& transform,
-                                      const SkIRect& clip_bounds) {
-  if (!cairo_)
-    return;  // Nothing to do.
-
-  LoadClipToContext(cairo_, clip_bounds);
-  LoadMatrixToContext(cairo_, transform);
-}
-
-// We use this static factory function instead of the regular constructor so
-// that we can create the pixel data before calling the constructor. This is
-// required so that we can call the base class' constructor with the pixel
-// data.
-BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height,
-                                                   bool is_opaque,
-                                                   cairo_surface_t* surface) {
-  if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
-    cairo_surface_destroy(surface);
-    return NULL;
-  }
-
-  // must call this before trying to install the surface, since that may result
-  // in the surface being destroyed.
-  cairo_t* cairo = cairo_create(surface);
-
-  SkBitmap bitmap;
-  if (!InstallCairoSurfacePixels(&bitmap, surface, is_opaque)) {
-    cairo_destroy(cairo);
-    return NULL;
-  }
-
-  // The device object will take ownership of the graphics context.
-  return new BitmapPlatformDevice(bitmap, cairo);
-}
-
-BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height,
-                                                   bool is_opaque) {
-  // This initializes the bitmap to all zeros.
-  cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
-                                                        width, height);
-
-  BitmapPlatformDevice* device = Create(width, height, is_opaque, surface);
-
-#ifndef NDEBUG
-    if (device && is_opaque)  // Fill with bright bluish green
-        SkCanvas(device).drawColor(0xFF00FF80);
-#endif
-
-  return device;
-}
-
-BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height,
-                                                   bool is_opaque,
-                                                   uint8_t* data) {
-  cairo_surface_t* surface = cairo_image_surface_create_for_data(
-      data, CAIRO_FORMAT_ARGB32, width, height,
-      cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
-
-  return Create(width, height, is_opaque, surface);
-}
-
-// Ownership of the cairo object is transferred.
-BitmapPlatformDevice::BitmapPlatformDevice(
-    const SkBitmap& bitmap,
-    cairo_t* cairo)
-    : SkBitmapDevice(bitmap),
-      cairo_(cairo) {
-  SetPlatformDevice(this, this);
-}
-
-BitmapPlatformDevice::~BitmapPlatformDevice() {
-  cairo_destroy(cairo_);
-}
-
-SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& info,
-                                                   const SkPaint*) {
-  SkASSERT(info.fInfo.colorType() == kN32_SkColorType);
-  return BitmapPlatformDevice::Create(info.fInfo.width(), info.fInfo.height(),
-                                      info.fInfo.isOpaque());
-}
-
-cairo_t* BitmapPlatformDevice::BeginPlatformPaint(
-      const SkMatrix& transform,
-      const SkIRect& clip_bounds) {
-  LoadConfig(transform, clip_bounds);
-  cairo_surface_t* surface = cairo_get_target(cairo_);
-  // Tell cairo to flush anything it has pending.
-  cairo_surface_flush(surface);
-  // Tell Cairo that we (probably) modified (actually, will modify) its pixel
-  // buffer directly.
-  cairo_surface_mark_dirty(surface);
-  return cairo_;
-}
-
-// PlatformCanvas impl
-
-std::unique_ptr<SkCanvas> CreatePlatformCanvasWithPixels(
-    int width,
-    int height,
-    bool is_opaque,
-    uint8_t* data,
-    OnFailureType failureType) {
-  sk_sp<SkBaseDevice> dev(
-      BitmapPlatformDevice::Create(width, height, is_opaque, data));
-  return CreateCanvas(dev, failureType);
-}
-
-}  // namespace skia
diff --git a/skia/ext/bitmap_platform_device_cairo.h b/skia/ext/bitmap_platform_device_cairo.h
deleted file mode 100644
index 5e6a6f3..0000000
--- a/skia/ext/bitmap_platform_device_cairo.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2013 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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_CAIRO_H_
-#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_CAIRO_H_
-
-#include <stdint.h>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "skia/ext/platform_device.h"
-
-typedef struct _cairo_surface cairo_surface_t;
-
-// -----------------------------------------------------------------------------
-// Image byte ordering on Linux:
-//
-// Pixels are packed into 32-bit words these days. Even for 24-bit images,
-// often 8-bits will be left unused for alignment reasons. Thus, when you see
-// ARGB as the byte order you have to wonder if that's in memory order or
-// little-endian order. Here I'll write A.R.G.B to specifiy the memory order.
-//
-// GdkPixbuf's provide a nice backing store and defaults to R.G.B.A order.
-// They'll do the needed byte swapping to match the X server when drawn.
-//
-// Skia can be controled in skia/include/corecg/SkUserConfig.h (see bits about
-// SK_R32_SHIFT). For Linux we define it to be ARGB in registers. For little
-// endian machines that means B.G.R.A in memory.
-//
-// The image loaders are controlled in
-// webkit/port/platform/image-decoders/ImageDecoder.h (see setRGBA). These are
-// also configured for ARGB in registers.
-//
-// Cairo's only 32-bit mode is ARGB in registers.
-//
-// X servers commonly have a 32-bit visual with xRGB in registers (since they
-// typically don't do alpha blending of drawables at the user level. Composite
-// extensions aside.)
-//
-// We don't use GdkPixbuf because its byte order differs from the rest. Most
-// importantly, it differs from Cairo which, being a system library, is
-// something that we can't easily change.
-// -----------------------------------------------------------------------------
-
-namespace skia {
-
-// -----------------------------------------------------------------------------
-// This is the Linux bitmap backing for Skia. We create a Cairo image surface
-// to store the backing buffer. This buffer is BGRA in memory (on little-endian
-// machines).
-//
-// For now we are also using Cairo to paint to the Drawables so we provide an
-// accessor for getting the surface.
-//
-// This is all quite ok for test_shell. In the future we will want to use
-// shared memory between the renderer and the main process at least. In this
-// case we'll probably create the buffer from a precreated region of memory.
-// -----------------------------------------------------------------------------
-class BitmapPlatformDevice : public SkBitmapDevice, public PlatformDevice {
- public:
-  // Create a BitmapPlatformDeviceLinux from an already constructed bitmap;
-  // you should probably be using Create(). This may become private later if
-  // we ever have to share state between some native drawing UI and Skia, like
-  // the Windows and Mac versions of this class do.
-  //
-  // This object takes ownership of @cairo.
-  BitmapPlatformDevice(const SkBitmap& other, cairo_t* cairo);
-  ~BitmapPlatformDevice() override;
-
-  // Constructs a device with size |width| * |height| with contents initialized
-  // to zero. |is_opaque| should be set if the caller knows the bitmap will be
-  // completely opaque and allows some optimizations.
-  static BitmapPlatformDevice* Create(int width, int height, bool is_opaque);
-
-  // This doesn't take ownership of |data|. If |data| is NULL, the contents
-  // of the device are initialized to 0.
-  static BitmapPlatformDevice* Create(int width, int height, bool is_opaque,
-                                      uint8_t* data);
-
- protected:
-  SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
-
- private:
-  // Overridden from PlatformDevice:
-  cairo_t* BeginPlatformPaint(const SkMatrix& transform,
-                              const SkIRect& clip_bounds) override;
-
-  static BitmapPlatformDevice* Create(int width, int height, bool is_opaque,
-                                      cairo_surface_t* surface);
-
-  // Loads the current transform and clip into the context.
-  void LoadConfig(const SkMatrix& transform, const SkIRect& clip_bounds);
-
-  // Graphics context used to draw into the surface.
-  cairo_t* cairo_;
-
-  DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice);
-};
-
-}  // namespace skia
-
-#endif  // SKIA_EXT_BITMAP_PLATFORM_DEVICE_CAIRO_H_
diff --git a/skia/ext/bitmap_platform_device_mac.cc b/skia/ext/bitmap_platform_device_mac.cc
deleted file mode 100644
index 67ad2a9..0000000
--- a/skia/ext/bitmap_platform_device_mac.cc
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (c) 2012 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 "skia/ext/bitmap_platform_device_mac.h"
-
-#import <ApplicationServices/ApplicationServices.h>
-#include <stddef.h>
-#include <time.h>
-
-#include "base/mac/mac_util.h"
-#include "base/memory/ref_counted.h"
-#include "skia/ext/bitmap_platform_device.h"
-#include "skia/ext/platform_canvas.h"
-#include "skia/ext/skia_utils_mac.h"
-#include "third_party/skia/include/core/SkMatrix.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "third_party/skia/include/core/SkRect.h"
-#include "third_party/skia/include/core/SkTypes.h"
-
-namespace skia {
-
-namespace {
-
-// Returns true if it is unsafe to attempt to allocate an offscreen buffer
-// given these dimensions.
-bool RasterDeviceTooBigToAllocate(int width, int height) {
-
-#ifndef SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX
-#define SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX    (2 * 256 * 1024 * 1024)
-#endif
-
-    int bytesPerPixel = 4;
-    int64_t bytes = (int64_t)width * height * bytesPerPixel;
-    return bytes > SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX;
-}
-
-static CGContextRef CGContextForData(void* data, int width, int height) {
-#define HAS_ARGB_SHIFTS(a, r, g, b) \
-            (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
-             && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
-#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
-  // Allocate a bitmap context with 4 components per pixel (BGRA).  Apple
-  // recommends these flags for improved CG performance.
-
-  // CGBitmapContextCreate returns NULL if width/height are 0. However, our
-  // callers expect to get a canvas back (which they later resize/reallocate)
-  // so we pin the dimensions here.
-  width = SkMax32(1, width);
-  height = SkMax32(1, height);
-  CGContextRef context =
-      CGBitmapContextCreate(data, width, height, 8, width * 4,
-                            base::mac::GetSystemColorSpace(),
-                            kCGImageAlphaPremultipliedFirst |
-                                kCGBitmapByteOrder32Host);
-#else
-#error We require that Skia's and CoreGraphics's recommended \
-       image memory layout match.
-#endif
-#undef HAS_ARGB_SHIFTS
-
-  if (!context)
-    return NULL;
-
-  // Change the coordinate system to match WebCore's
-  CGContextTranslateCTM(context, 0, height);
-  CGContextScaleCTM(context, 1.0, -1.0);
-
-  return context;
-}
-
-}  // namespace
-
-void BitmapPlatformDevice::ReleaseBitmapContext() {
-  SkASSERT(bitmap_context_);
-  CGContextRelease(bitmap_context_);
-  bitmap_context_ = NULL;
-}
-
-// Loads the specified Skia transform into the device context
-static void LoadTransformToCGContext(CGContextRef context,
-                                     const SkMatrix& matrix) {
-  // CoreGraphics can concatenate transforms, but not reset the current one.
-  // So in order to get the required behavior here, we need to first make
-  // the current transformation matrix identity and only then load the new one.
-
-  // Reset matrix to identity.
-  CGAffineTransform orig_cg_matrix = CGContextGetCTM(context);
-  CGAffineTransform orig_cg_matrix_inv =
-      CGAffineTransformInvert(orig_cg_matrix);
-  CGContextConcatCTM(context, orig_cg_matrix_inv);
-
-  // assert that we have indeed returned to the identity Matrix.
-  SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context)));
-
-  // Convert xform to CG-land.
-  // Our coordinate system is flipped to match WebKit's so we need to modify
-  // the xform to match that.
-  SkMatrix transformed_matrix = matrix;
-  SkScalar sy = -matrix.getScaleY();
-  transformed_matrix.setScaleY(sy);
-  size_t height = CGBitmapContextGetHeight(context);
-  SkScalar ty = -matrix.getTranslateY();  // y axis is flipped.
-  transformed_matrix.setTranslateY(ty + (SkScalar)height);
-
-  CGAffineTransform cg_matrix =
-      skia::SkMatrixToCGAffineTransform(transformed_matrix);
-
-  // Load final transform into context.
-  CGContextConcatCTM(context, cg_matrix);
-}
-
-static void LoadClippingRegionToCGContext(CGContextRef context,
-                                          const SkIRect& clip_bounds,
-                                          const SkMatrix& transformation) {
-  // CoreGraphics applies the current transform to clip rects, which is
-  // unwanted. Inverse-transform the rect before sending it to CG. This only
-  // works for translations and scaling, but not for rotations (but the
-  // viewport is never rotated anyway).
-  SkMatrix t;
-  bool did_invert = transformation.invert(&t);
-  if (!did_invert)
-    t.reset();
-
-  SkRect rect = SkRect::Make(clip_bounds);
-  t.mapRect(&rect);
-  SkIRect irect;
-  rect.round(&irect);
-  CGContextClipToRect(context, skia::SkIRectToCGRect(irect));
-}
-
-void BitmapPlatformDevice::LoadConfig(const SkMatrix& transform,
-                                      const SkIRect& clip_bounds) {
-  if (!bitmap_context_)
-    return;  // Nothing to do.
-
-  // We must restore and then save the state of the graphics context since the
-  // calls to Load the clipping region to the context are strictly cummulative,
-  // i.e., you can't replace a clip rect, other than with a save/restore.
-  // But this implies that no other changes to the state are done elsewhere.
-  // If we ever get to need to change this, then we must replace the clip rect
-  // calls in LoadClippingRegionToCGContext() with an image mask instead.
-  CGContextRestoreGState(bitmap_context_);
-  CGContextSaveGState(bitmap_context_);
-  LoadTransformToCGContext(bitmap_context_, transform);
-  LoadClippingRegionToCGContext(bitmap_context_, clip_bounds, transform);
-}
-
-
-// We use this static factory function instead of the regular constructor so
-// that we can create the pixel data before calling the constructor. This is
-// required so that we can call the base class' constructor with the pixel
-// data.
-BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context,
-                                                   int width,
-                                                   int height,
-                                                   bool is_opaque,
-                                                   bool do_clear) {
-  if (RasterDeviceTooBigToAllocate(width, height))
-    return NULL;
-
-  SkBitmap bitmap;
-  // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
-  // rowbytes for the case when context != NULL.
-  bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType));
-
-  void* data;
-  if (context) {
-    data = CGBitmapContextGetData(context);
-    bitmap.setPixels(data);
-  } else {
-    if (!bitmap.tryAllocPixels())
-      return NULL;
-    data = bitmap.getPixels();
-  }
-  if (do_clear)
-    memset(data, 0, bitmap.getSafeSize());
-
-  // If we were given data, then don't clobber it!
-#ifndef NDEBUG
-  if (!context && is_opaque) {
-    // To aid in finding bugs, we set the background color to something
-    // obviously wrong so it will be noticable when it is not cleared
-    bitmap.eraseARGB(255, 0, 255, 128);  // bright bluish green
-  }
-#endif
-
-  if (!context) {
-    context = CGContextForData(data, width, height);
-    if (!context)
-      return NULL;
-  } else
-    CGContextRetain(context);
-
-  BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap);
-
-  // The device object took ownership of the graphics context with its own
-  // CGContextRetain call.
-  CGContextRelease(context);
-
-  return rv;
-}
-
-BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data,
-                                                           int width,
-                                                           int height,
-                                                           bool is_opaque) {
-  CGContextRef context = NULL;
-  if (data)
-    context = CGContextForData(data, width, height);
-
-  BitmapPlatformDevice* rv = Create(context, width, height, is_opaque, false);
-
-  // The device object took ownership of the graphics context with its own
-  // CGContextRetain call.
-  if (context)
-    CGContextRelease(context);
-
-  return rv;
-}
-
-// The device will own the bitmap, which corresponds to also owning the pixel
-// data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
-BitmapPlatformDevice::BitmapPlatformDevice(
-    CGContextRef context, const SkBitmap& bitmap)
-    : SkBitmapDevice(bitmap),
-      bitmap_context_(context) {
-  SetPlatformDevice(this, this);
-  SkASSERT(bitmap_context_);
-  // Initialize the clip region to the entire bitmap.
-
-  SkIRect rect;
-  rect.set(0, 0,
-           CGBitmapContextGetWidth(bitmap_context_),
-           CGBitmapContextGetHeight(bitmap_context_));
-  CGContextRetain(bitmap_context_);
-  // We must save the state once so that we can use the restore/save trick
-  // in LoadConfig().
-  CGContextSaveGState(bitmap_context_);
-}
-
-BitmapPlatformDevice::~BitmapPlatformDevice() {
-  if (bitmap_context_)
-    CGContextRelease(bitmap_context_);
-}
-
-NativeDrawingContext BitmapPlatformDevice::BeginPlatformPaint(
-    const SkMatrix& transform,
-    const SkIRect& clip_bounds) {
-  LoadConfig(transform, clip_bounds);
-  return bitmap_context_;
-}
-
-SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& cinfo,
-                                                   const SkPaint*) {
-  const SkImageInfo& info = cinfo.fInfo;
-  const bool do_clear = !info.isOpaque();
-  SkASSERT(info.colorType() == kN32_SkColorType);
-  return Create(NULL, info.width(), info.height(), info.isOpaque(), do_clear);
-}
-
-// PlatformCanvas impl
-
-std::unique_ptr<SkCanvas> CreatePlatformCanvasWithPixels(
-    int width,
-    int height,
-    bool is_opaque,
-    uint8_t* data,
-    OnFailureType failureType) {
-  sk_sp<SkBaseDevice> dev(
-      BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque));
-  return CreateCanvas(dev, failureType);
-}
-
-}  // namespace skia
diff --git a/skia/ext/bitmap_platform_device_mac.h b/skia/ext/bitmap_platform_device_mac.h
deleted file mode 100644
index 10dce7fe..0000000
--- a/skia/ext/bitmap_platform_device_mac.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2012 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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_
-#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_
-
-#include <stdint.h>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "skia/ext/platform_device.h"
-
-namespace skia {
-
-// A device is basically a wrapper around SkBitmap that provides a surface for
-// SkCanvas to draw into. Our device provides a surface CoreGraphics can also
-// write to. BitmapPlatformDevice creates a bitmap using
-// CGCreateBitmapContext() in a format that Skia supports and can then use this
-// to draw text into, etc. This pixel data is provided to the bitmap that the
-// device contains so that it can be shared.
-//
-// The device owns the pixel data, when the device goes away, the pixel data
-// also becomes invalid. THIS IS DIFFERENT THAN NORMAL SKIA which uses
-// reference counting for the pixel data. In normal Skia, you could assign
-// another bitmap to this device's bitmap and everything will work properly.
-// For us, that other bitmap will become invalid as soon as the device becomes
-// invalid, which may lead to subtle bugs. Therefore, DO NOT ASSIGN THE
-// DEVICE'S PIXEL DATA TO ANOTHER BITMAP, make sure you copy instead.
-class SK_API BitmapPlatformDevice : public SkBitmapDevice, public PlatformDevice {
- public:
-  // Creates a BitmapPlatformDevice instance. |is_opaque| should be set if the
-  // caller knows the bitmap will be completely opaque and allows some
-  // optimizations.
-  // |context| may be NULL. If |context| is NULL, then the bitmap backing store
-  // is not initialized.
-  static BitmapPlatformDevice* Create(CGContextRef context,
-                                      int width, int height,
-                                      bool is_opaque, bool do_clear = false);
-
-  // Creates a context for |data| and calls Create.
-  // If |data| is NULL, then the bitmap backing store is not initialized.
-  static BitmapPlatformDevice* CreateWithData(uint8_t* data,
-                                              int width, int height,
-                                              bool is_opaque);
-
-  ~BitmapPlatformDevice() override;
-
- protected:
-  BitmapPlatformDevice(CGContextRef context,
-                       const SkBitmap& bitmap);
-
-  SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
-
- private:
-  NativeDrawingContext BeginPlatformPaint(const SkMatrix& transform,
-                                          const SkIRect& clip_bounds) override;
-
-  void ReleaseBitmapContext();
-
-  // Loads the current transform and clip into the context. Can be called even
-  // when |bitmap_context_| is NULL (will be a NOP).
-  void LoadConfig(const SkMatrix& transform, const SkIRect& clip_bounds);
-
-  // Lazily-created graphics context used to draw into the bitmap.
-  CGContextRef bitmap_context_;
-
-  DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice);
-};
-
-}  // namespace skia
-
-#endif  // SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_
diff --git a/skia/ext/bitmap_platform_device_skia.cc b/skia/ext/bitmap_platform_device_skia.cc
index a39e5d71..5904d6f2 100644
--- a/skia/ext/bitmap_platform_device_skia.cc
+++ b/skia/ext/bitmap_platform_device_skia.cc
@@ -9,15 +9,7 @@
 
 BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height,
                                                    bool is_opaque) {
-  SkBitmap bitmap;
-  if (bitmap.tryAllocN32Pixels(width, height, is_opaque)) {
-    // Follow the logic in SkCanvas::createDevice(), initialize the bitmap if it
-    // is not opaque.
-    if (!is_opaque)
-      bitmap.eraseARGB(0, 0, 0, 0);
-    return new BitmapPlatformDevice(bitmap);
-  }
-  return NULL;
+  return Create(width, height, is_opaque, nullptr);
 }
 
 BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height,
@@ -26,10 +18,17 @@
   SkBitmap bitmap;
   bitmap.setInfo(SkImageInfo::MakeN32(width, height,
       is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType));
-  if (data)
+
+  if (data) {
     bitmap.setPixels(data);
-  else if (!bitmap.tryAllocPixels())
-    return NULL;
+  } else {
+      if (!bitmap.tryAllocPixels())
+        return nullptr;
+      // Follow the logic in SkCanvas::createDevice(), initialize the bitmap if
+      // it is not opaque.
+      if (!is_opaque)
+        bitmap.eraseARGB(0, 0, 0, 0);
+  }
 
   return new BitmapPlatformDevice(bitmap);
 }
@@ -49,15 +48,6 @@
                                       info.fInfo.isOpaque());
 }
 
-NativeDrawingContext BitmapPlatformDevice::BeginPlatformPaint(
-    const SkMatrix& transform,
-    const SkIRect& clip_bounds) {
-  // TODO(zhenghao): What should we return? The ptr to the address of the
-  // pixels? Maybe this won't be called at all.
-  SkPixmap pixmap;
-  return accessPixels(&pixmap) ? pixmap.writable_addr() : nullptr;
-}
-
 // PlatformCanvas impl
 
 std::unique_ptr<SkCanvas> CreatePlatformCanvasWithPixels(
diff --git a/skia/ext/bitmap_platform_device_skia.h b/skia/ext/bitmap_platform_device_skia.h
index 024064f..42a3be1 100644
--- a/skia/ext/bitmap_platform_device_skia.h
+++ b/skia/ext/bitmap_platform_device_skia.h
@@ -21,15 +21,21 @@
 // shared memory between the renderer and the main process at least. In this
 // case we'll probably create the buffer from a precreated region of memory.
 // -----------------------------------------------------------------------------
-class BitmapPlatformDevice : public SkBitmapDevice, public PlatformDevice {
+class BitmapPlatformDevice final : public SkBitmapDevice,
+                                   public PlatformDevice {
  public:
   // Construct a BitmapPlatformDevice. |is_opaque| should be set if the caller
-  // knows the bitmap will be completely opaque and allows some optimizations.
-  // The bitmap is not initialized.
+  // knows the bitmap will be completely opaque and allows some optimizations
+  // (the bitmap is not initialized to 0 when is_opaque == true).
   static BitmapPlatformDevice* Create(int width, int height, bool is_opaque);
 
-  // This doesn't take ownership of |data|. If |data| is null, the bitmap
-  // is not initialized to 0.
+  // This doesn't take ownership of |data|. If |data| is null and |is_opaque|
+  // is false, the bitmap is initialized to 0.
+  //
+  // Note: historicaly, BitmapPlatformDevice impls have had diverging
+  // initialization behavior for null |data| (Cairo used to initialize, while
+  // the others did not).  For now we stick to the more conservative Cairo
+  // behavior.
   static BitmapPlatformDevice* Create(int width, int height, bool is_opaque,
                                       uint8_t* data);
 
@@ -44,9 +50,6 @@
   SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
 
  private:
-  NativeDrawingContext BeginPlatformPaint(const SkMatrix& transform,
-                                          const SkIRect& clip_bounds) override;
-
   DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice);
 };
 
diff --git a/skia/ext/bitmap_platform_device_win.h b/skia/ext/bitmap_platform_device_win.h
index 82f17ff3..6f359e2 100644
--- a/skia/ext/bitmap_platform_device_win.h
+++ b/skia/ext/bitmap_platform_device_win.h
@@ -17,8 +17,8 @@
 // format that Skia supports and can then use this to draw ClearType into, etc.
 // This pixel data is provided to the bitmap that the device contains so that it
 // can be shared.
-class SK_API BitmapPlatformDevice : public SkBitmapDevice,
-                                    public PlatformDevice {
+class SK_API BitmapPlatformDevice final : public SkBitmapDevice,
+                                          public PlatformDevice {
  public:
   // Factory function. is_opaque should be set if the caller knows the bitmap
   // will be completely opaque and allows some optimizations.
diff --git a/skia/ext/platform_device.h b/skia/ext/platform_device.h
index b756aa6..f349a49 100644
--- a/skia/ext/platform_device.h
+++ b/skia/ext/platform_device.h
@@ -46,11 +46,14 @@
  public:
   virtual ~PlatformDevice();
 
+// Only implemented in bitmap_platform_device_win.
+#if defined(WIN32)
   // The DC that corresponds to the bitmap, used for GDI operations drawing
   // into the bitmap. This is possibly heavyweight, so it should be existant
   // only during one pass of rendering.
   virtual NativeDrawingContext BeginPlatformPaint(const SkMatrix& transform,
                                                   const SkIRect& clip_bounds) = 0;
+#endif
 };
 
 }  // namespace skia
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index a2d037a..569c9459 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8,6 +8,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -88,6 +91,14 @@
     ],
     "instrumentation_tests": [
       {
+        "apk_under_test": "Blimp.apk",
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "test_apk": "BlimpTest.apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
@@ -107,6 +118,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -190,6 +204,14 @@
         "test": "android_webview_test_apk"
       },
       {
+        "apk_under_test": "Blimp.apk",
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "test_apk": "BlimpTest.apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
@@ -209,6 +231,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -301,6 +326,13 @@
         "timeout_scale": 4
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "timeout_scale": 4
+      },
+      {
         "test": "chrome_public_test_apk",
         "timeout_scale": 4
       },
@@ -1374,6 +1406,12 @@
         "test": "base_unittests"
       },
       {
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -1537,6 +1575,12 @@
         "test": "android_webview_test_apk"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
@@ -1568,6 +1612,12 @@
         "test": "base_unittests"
       },
       {
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -1731,6 +1781,12 @@
         "test": "android_webview_test_apk"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 73f97b4c..cfcafa1 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -104,6 +104,72 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "hard_timeout": 960,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1150,6 +1216,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -1236,6 +1305,14 @@
         "test": "android_webview_test_apk"
       },
       {
+        "apk_under_test": "Blimp.apk",
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "test_apk": "BlimpTest.apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
@@ -1315,6 +1392,39 @@
         "test": "base_unittests"
       },
       {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "LMY48I",
+              "device_type": "hammerhead"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -2183,6 +2293,42 @@
       },
       {
         "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "LMY48I",
+              "device_type": "hammerhead"
+            }
+          ],
+          "expiration": 10800,
+          "hard_timeout": 960,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_compile_targets": [
           "chrome_public_test_apk"
         ],
         "override_isolate_target": "chrome_public_test_apk",
@@ -2300,6 +2446,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -2386,6 +2535,14 @@
         "test": "android_webview_test_apk"
       },
       {
+        "apk_under_test": "Blimp.apk",
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "test_apk": "BlimpTest.apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
@@ -2495,6 +2652,71 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "hard_timeout": 600,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "hard_timeout": 960,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -3450,6 +3672,9 @@
         "test": "base_unittests"
       },
       {
+        "test": "blimp_unittests"
+      },
+      {
         "override_compile_targets": [
           "breakpad_unittests_deps"
         ],
@@ -3536,6 +3761,14 @@
         "test": "android_webview_test_apk"
       },
       {
+        "apk_under_test": "Blimp.apk",
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "test": "blimp_test_apk",
+        "test_apk": "BlimpTest.apk"
+      },
+      {
         "test": "chrome_public_test_apk"
       },
       {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c3cde1d0..c4e9c24 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -265,6 +265,36 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_type": "gce_x86"
+            }
+          ],
+          "hard_timeout": 600
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_type": "gce_x86"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -906,6 +936,35 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_type": "coho"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_type": "coho"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1514,6 +1573,122 @@
       }
     ]
   },
+  "Blimp Android Client": {
+    "gtest_tests": [
+      {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "hard_timeout": 960,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "MMB29Q",
+              "device_type": "bullhead"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      }
+    ]
+  },
+  "Blimp Linux Engine": {
+    "gtest_tests": [
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "base_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_browsertests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "content_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "ipc_tests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "net_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "sandbox_linux_unittests"
+      }
+    ]
+  },
   "Browser Side Navigation Linux": {
     "gtest_tests": [
       {
@@ -1603,6 +1778,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "blink_heap_unittests"
       },
       {
@@ -1980,6 +2161,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "blink_heap_unittests"
       },
       {
@@ -3587,6 +3774,35 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_type": "coho"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_type": "coho"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -11136,6 +11352,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "blink_heap_unittests"
       },
       {
@@ -12764,6 +12986,23 @@
         "args": [
           "--test-arguments=--site-per-process"
         ],
+        "name": "site_per_process_blimp_unittests",
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_type": "bullhead"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "args": [
+          "--test-arguments=--site-per-process"
+        ],
         "name": "site_per_process_components_browsertests",
         "override_isolate_target": "components_browsertests",
         "swarming": {
@@ -12809,6 +13048,15 @@
       },
       {
         "args": [
+          "--site-per-process"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "args": [
           "--site-per-process",
           "--test-launcher-filter-file=../../testing/buildbot/filters/site-per-process.browser_tests.filter"
         ],
@@ -13070,6 +13318,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "blink_heap_unittests"
       },
       {
@@ -13426,6 +13680,12 @@
         "swarming": {
           "can_use_on_swarming_builders": true
         },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
         "test": "blink_heap_unittests"
       },
       {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index e71cdc8..84215c51 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -120,6 +120,72 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "KTU84P",
+              "device_type": "hammerhead"
+            }
+          ],
+          "hard_timeout": 600,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "KTU84P",
+              "device_type": "hammerhead"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1329,6 +1395,72 @@
         "test": "base_unittests"
       },
       {
+        "override_compile_targets": [
+          "blimp_test_apk"
+        ],
+        "override_isolate_target": "blimp_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "KTU84P",
+              "device_type": "hammerhead"
+            }
+          ],
+          "hard_timeout": 600,
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_test_apk"
+      },
+      {
+        "override_isolate_target": "blimp_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:25755a2c316937ee44a6432163dc5e2f9c85cf58"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "4",
+              "device_os": "KTU84P",
+              "device_type": "hammerhead"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blimp_unittests"
+      },
+      {
         "override_isolate_target": "blink_heap_unittests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -2438,6 +2570,22 @@
       }
     ]
   },
+  "Blimp Linux (dbg)": {
+    "gtest_tests": [
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_browsertests"
+      },
+      {
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "blimp_unittests"
+      }
+    ]
+  },
   "Cast Linux": {
     "gtest_tests": [
       {
@@ -2651,6 +2799,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "shards": 5
         },
@@ -3302,6 +3456,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "shards": 10
         },
@@ -3849,6 +4009,12 @@
       },
       {
         "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "blimp_unittests"
+      },
+      {
+        "swarming": {
           "can_use_on_swarming_builders": true,
           "shards": 10
         },
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index fe019ae9..67280c5 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -163,6 +163,18 @@
     "label": "//tools/battor_agent:battor_agent_unittests",
     "type": "console_test_launcher",
   },
+  "blimp_browsertests": {
+    "label": "//blimp:blimp_browsertests",
+    "type": "console_test_launcher",
+  },
+  "blimp_test_apk": {
+    "label": "//blimp/client/app:blimp_test_apk",
+    "type": "console_test_launcher",
+  },
+  "blimp_unittests": {
+    "label": "//blimp:blimp_unittests",
+    "type": "console_test_launcher",
+  },
   "blink_heap_unittests": {
     "label": "//third_party/WebKit/Source/platform/heap:blink_heap_unittests",
     "type": "console_test_launcher",
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 74ad016..658384e 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1698,7 +1698,6 @@
 crbug.com/600248 imported/wpt/web-animations/interfaces/Animation/onfinish.html [ Pass Failure ]
 crbug.com/600248 imported/wpt/web-animations/interfaces/Animation/playbackRate.html [ Pass Failure ]
 
-crbug.com/600248 imported/wpt/web-animations/animation-model/animation-types/type-per-property.html [ Failure Timeout ]
 crbug.com/600248 imported/wpt/web-animations/timing-model/animations/updating-the-finished-state.html [ Pass Failure Timeout ]
 
 crbug.com/611658 [ Win7 ] fast/forms/text/text-font-height-mismatch.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/fast/harness/resources/results-test.js b/third_party/WebKit/LayoutTests/fast/harness/resources/results-test.js
index 20e8541e..2d06f39 100644
--- a/third_party/WebKit/LayoutTests/fast/harness/resources/results-test.js
+++ b/third_party/WebKit/LayoutTests/fast/harness/resources/results-test.js
@@ -27,7 +27,6 @@
         "fixable": 0,
         "num_flaky": 0,
         "layout_tests_dir": "/WEBKITROOT",
-        "has_pretty_patch": false,
         "has_wdiff": false,
         "chromium_revision": 12345,
         "pixel_tests_enabled": true
@@ -93,7 +92,7 @@
     } catch (e) {
         logFail("FAIL: uncaught exception " + e.toString());
     }
-    
+
     try {
         assertions();
     } catch (e) {
@@ -126,7 +125,7 @@
         assertTrue(document.querySelector('tbody td:nth-child(4)').textContent == actual);
         assertTrue(document.querySelector('tbody td:nth-child(5)').textContent == expected);
     });
-    
+
 }
 
 function runTests()
@@ -176,14 +175,14 @@
         assertTrue(testLinks[0].textContent == 'foo/bar.html');
         assertTrue(testLinks[1].textContent == 'foo/bar1.html');
         assertTrue(testLinks[2].textContent == 'foo/bar2.html');
-        
+
         assertTrue(!document.querySelector('#passes-table .expand-button'));
 
         var expectationTypes = document.querySelectorAll('#passes-table td:last-of-type');
         assertTrue(expectationTypes[0].textContent == 'TEXT');
         assertTrue(expectationTypes[1].textContent == 'CRASH');
         assertTrue(expectationTypes[2].textContent == 'IMAGE');
-        
+
         assertTrue(document.getElementById('crash-tests-table'));
         assertTrue(document.getElementById('crash-tests-table').textContent.indexOf('crash log') != -1);
         assertTrue(document.getElementById('timeout-tests-table'));
@@ -216,14 +215,14 @@
         var expandLinks = document.querySelectorAll('.expand-button-text');
         for (var i = 0; i < expandLinks.length; i++)
             assertTrue(isExpanded(expandLinks[i]));
-        
+
         collapseAllExpectations();
         // Collapsed expectations stay in the dom, but are display:none.
         assertTrue(document.querySelectorAll('tbody tr').length == 8);
         var expandLinks = document.querySelectorAll('.expand-button-text');
         for (var i = 0; i < expandLinks.length; i++)
             assertTrue(isCollapsed(expandLinks[i]));
-            
+
         expandExpectations(expandLinks[1]);
         assertTrue(isCollapsed(expandLinks[0]));
         assertTrue(isExpanded(expandLinks[1]));
@@ -246,19 +245,19 @@
         assertTrue(visibleExpandLinks().length == 1);
         assertTrue(document.querySelectorAll('.results-row').length == 1);
         assertTrue(window.getComputedStyle(document.querySelectorAll('tbody')[0], null)['display'] == 'none');
-        
+
         document.getElementById('show-expected-failures').checked = true;
         document.getElementById('show-expected-failures').onchange();
 
         assertTrue(visibleExpandLinks().length == 2);
         assertTrue(document.querySelectorAll('.results-row').length == 1);
         assertTrue(window.getComputedStyle(document.querySelectorAll('tbody')[0], null)['display'] != 'none');
-        
+
         expandAllExpectations();
         assertTrue(document.querySelectorAll('.results-row').length == 2);
         assertTrue(window.getComputedStyle(document.querySelectorAll('tbody')[0], null)['display'] != 'none');
     });
-    
+
     results = mockResults();
     results.tests['only-expected-fail.html'] = mockExpectation('TEXT', 'TEXT');
     runTest(results, function() {
@@ -266,16 +265,16 @@
     });
 
     runDefaultSingleRowTest('bar-skip.html', 'TEXT', 'SKIP', true, '', '');
-    runDefaultSingleRowTest('bar-flaky-fail.html', 'PASS FAIL', 'TEXT', true, 'expected actual diff ', '');
+    runDefaultSingleRowTest('bar-flaky-fail.html', 'PASS FAIL', 'TEXT', true, 'expected actual diff pretty diff ', '');
     runDefaultSingleRowTest('bar-flaky-fail-unexpected.html', 'PASS TEXT', 'IMAGE', false, '', 'images diff ');
     runDefaultSingleRowTest('bar-audio.html', 'TEXT', 'AUDIO', false, 'expected audio actual audio ', '');
     runDefaultSingleRowTest('bar-image.html', 'TEXT', 'IMAGE', false, '', 'images diff ');
-    runDefaultSingleRowTest('bar-image-plus-text.html', 'TEXT', 'IMAGE+TEXT', false, 'expected actual diff ', 'images diff ');
+    runDefaultSingleRowTest('bar-image-plus-text.html', 'TEXT', 'IMAGE+TEXT', false, 'expected actual diff pretty diff ', 'images diff ');
 
-    // test the mapping for FAIL onto only ['TEXT', 'IMAGE+TEXT', 'AUDIO']
-    runDefaultSingleRowTest('bar-image.html', 'FAIL', 'IMAGE+TEXT', true, 'expected actual diff ', 'images diff ');
+    // Test the mapping for FAIL onto only ['IMAGE+TEXT', 'AUDIO', 'TEXT', 'IMAGE'].
+    runDefaultSingleRowTest('bar-image.html', 'FAIL', 'IMAGE+TEXT', true, 'expected actual diff pretty diff ', 'images diff ');
     runDefaultSingleRowTest('bar-image.html', 'FAIL', 'AUDIO', true, 'expected audio actual audio ', '');
-    runDefaultSingleRowTest('bar-image.html', 'FAIL', 'TEXT', true, 'expected actual diff ', '');
+    runDefaultSingleRowTest('bar-image.html', 'FAIL', 'TEXT', true, 'expected actual diff pretty diff ', '');
     runDefaultSingleRowTest('bar-image.html', 'FAIL', 'IMAGE', false, '', 'images diff ');
 
     results = mockResults();
@@ -346,10 +345,8 @@
     results = mockResults();
     var subtree = results.tests['foo'] = {}
     subtree['bar.html'] = mockExpectation('TEXT', 'TEXT');
-    results.has_pretty_patch = true;
     runTest(results, function() {
         assertTrue(document.querySelector('tbody td:nth-child(2)').textContent.indexOf('pretty diff') != -1);
-        assertTrue(document.querySelector('tbody td:nth-child(2)').textContent.indexOf('wdiff') == -1);
     });
 
     results = mockResults();
@@ -358,9 +355,9 @@
     results.has_wdiff = true;
     runTest(results, function() {
         assertTrue(document.querySelector('tbody td:nth-child(2)').textContent.indexOf('wdiff') != -1);
-        assertTrue(document.querySelector('tbody td:nth-child(2)').textContent.indexOf('pretty diff') == -1);
+
     });
-    
+
     results = mockResults();
     var subtree = results.tests['foo'] = {}
     subtree['bar.html'] = mockExpectation('TEXT', 'PASS');
@@ -404,7 +401,7 @@
         assertTrue(!!document.querySelector('.pixel-zoom-container'));
         assertTrue(document.querySelectorAll('.zoom-image-container').length == 3);
     });
-    
+
     results = mockResults();
     var subtree = results.tests['fullscreen'] = {}
     subtree['full-screen-api.html'] = mockExpectation('TEXT', 'IMAGE+TEXT');
@@ -416,7 +413,7 @@
 
     var oldShouldUseTracLinks = shouldUseTracLinks;
     shouldUseTracLinks = function() { return true; };
-    
+
     results = mockResults();
     var subtree = results.tests['fullscreen'] = {}
     subtree['full-screen-api.html'] = mockExpectation('TEXT', 'IMAGE+TEXT');
@@ -446,12 +443,12 @@
         updateTogglingImages();
         // FIXME: We get extra spaces in the DOM every time we enable/disable image toggling.
         assertTrue(document.querySelector('tbody td:nth-child(3)').textContent == 'expected actual  diff ');
-        
+
         document.getElementById('toggle-images').checked = true;
         updateTogglingImages();
         assertTrue(document.querySelector('tbody td:nth-child(3)').textContent == ' images   diff ');
     });
-    
+
     results = mockResults();
     results.tests['reading-options-from-localstorage.html'] = mockExpectation('IMAGE+TEXT', 'IMAGE+TEXT');
     runTest(results, function() {
@@ -555,7 +552,7 @@
         expandAllExpectations();
         assertTrue(visibleExpandLinks().length == 2);
     });
-    
+
 
     results = mockResults();
     var subtree = results.tests['foo'] = {}
@@ -594,7 +591,7 @@
     runTest(results, function() {
         assertTrue(document.getElementById('results-table'));
         assertTrue(visibleExpandLinks().length == 3);
-        
+
         if (window.eventSender) {
             eventSender.keyDown('i', ["metaKey"]);
             eventSender.keyDown('i', ["shiftKey"]);
diff --git a/third_party/WebKit/LayoutTests/fast/harness/results-expected.txt b/third_party/WebKit/LayoutTests/fast/harness/results-expected.txt
index ade95c5..02d8135 100644
--- a/third_party/WebKit/LayoutTests/fast/harness/results-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/harness/results-expected.txt
@@ -131,8 +131,6 @@
 TEST-25: PASS
 TEST-25: PASS
 TEST-26: PASS
-TEST-26: PASS
-TEST-27: PASS
 TEST-27: PASS
 TEST-28: PASS
 TEST-29: PASS
diff --git a/third_party/WebKit/LayoutTests/fast/harness/results.html b/third_party/WebKit/LayoutTests/fast/harness/results.html
index 7982f1b..0166837a 100644
--- a/third_party/WebKit/LayoutTests/fast/harness/results.html
+++ b/third_party/WebKit/LayoutTests/fast/harness/results.html
@@ -539,10 +539,8 @@
 {
     var html = resultLink(prefix, '-expected.txt', 'expected') +
         resultLink(prefix, '-actual.txt', 'actual') +
-        resultLink(prefix, '-diff.txt', 'diff');
-
-    if (globalState().results.has_pretty_patch)
-        html += resultLink(prefix, '-pretty-diff.html', 'pretty diff');
+        resultLink(prefix, '-diff.txt', 'diff') +
+        resultLink(prefix, '-pretty-diff.html', 'pretty diff');
 
     if (globalState().results.has_wdiff)
         html += resultLink(prefix, '-wdiff.html', 'wdiff');
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.html b/third_party/WebKit/LayoutTests/imported/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.html
index 86a29d1..b74b3f2 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/FileAPI/reading-data-section/FileReader-multiple-reads.html
@@ -62,8 +62,8 @@
   var blob_2 = new Blob(['TEST000000002'])
   var reader = new FileReader();
   reader.onloadend = this.step_func_done(function() {
-    assert_equals(reader.readyState, FileReader.LOADING,
-                  "readyState must be LOADING")
+    assert_equals(reader.readyState, FileReader.DONE,
+                  "readyState must be DONE")
     reader.readAsArrayBuffer(blob_2)
     assert_equals(reader.readyState, FileReader.LOADING, "readyState Must be LOADING")
   });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbindex-rename.html b/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbindex-rename.html
index 2ef26d29..370b83e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbindex-rename.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbindex-rename.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<meta name="timeout" content="long">
 <title>IndexedDB: index renaming support</title>
 <link rel="help"
       href="https://w3c.github.io/IndexedDB/#dom-idbindex-name">
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbobjectstore-rename-store.html b/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbobjectstore-rename-store.html
index 47860b59..d07a464 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbobjectstore-rename-store.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/IndexedDB/idbobjectstore-rename-store.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<meta name="timeout" content="long">
 <title>IndexedDB: object store renaming support</title>
 <link rel="help"
       href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name">
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/WebIDL/current-realm.html b/third_party/WebKit/LayoutTests/imported/wpt/WebIDL/current-realm.html
index fd24709b..c9450e2 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/WebIDL/current-realm.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/WebIDL/current-realm.html
@@ -134,6 +134,9 @@
    test(function() {
      var c = new self[0].FontFace("test", "about:blank"),
          obj = c.load()
+     obj.catch(function(reason) {
+       // Ignore exception when it fails to load because the URL is invalid.
+     });
      assert_global(obj)
 
      obj = FontFace.prototype.load.call(c)
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/check_stability.py b/third_party/WebKit/LayoutTests/imported/wpt/check_stability.py
index 421f8cb..b8ede38 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/check_stability.py
+++ b/third_party/WebKit/LayoutTests/imported/wpt/check_stability.py
@@ -536,14 +536,19 @@
             except ValueError:
                 pass
         if pr_number:
-            logger.info("### [%s](%s/%s%s) ###" % (test, baseurl, pr_number, test))
+            logger.info("<details>\n")
+            logger.info('<summary><a href="%s/%s%s">%s</a></summary>\n\n' %
+                        (baseurl, pr_number, test, test))
         else:
             logger.info("### %s ###" % test)
         parent = test_results.pop(None)
         strings = [("", err_string(parent, iterations))]
-        strings.extend(((("`%s`" % markdown_adjust(subtest)) if subtest else "", err_string(results, iterations))
+        strings.extend(((("`%s`" % markdown_adjust(subtest)) if subtest
+                         else "", err_string(results, iterations))
                         for subtest, results in test_results.iteritems()))
         table(["Subtest", "Results"], strings, logger.info)
+        if pr_number:
+            logger.info("</details>\n")
 
 
 def get_parser():
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/dom/historical.html b/third_party/WebKit/LayoutTests/imported/wpt/dom/historical.html
index e0612be..d669ad4a 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/dom/historical.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/dom/historical.html
@@ -48,11 +48,22 @@
   "renameNode",
   "defaultCharset",
   "height",
-  "width"
+  "width",
+  // https://github.com/whatwg/html/commit/a64aea7fdb221bba027d95dc3cabda09e0b3e5dc
+  "commands",
+  // https://github.com/whatwg/html/commit/797b4d273955a0fe3cc2e2d0ca5d578f37c0f126
+  "cssElementMap",
+  // https://github.com/whatwg/html/commit/e236f46820b93d6fe2e2caae0363331075c6c4fb
+  "async",
 ]
 documentNuked.forEach(isNukedFromDocument)
 
 test(function() {
+  // https://github.com/whatwg/html/commit/e236f46820b93d6fe2e2caae0363331075c6c4fb
+  assert_false("load" in document);
+}, "document.load");
+
+test(function() {
   assert_equals(document.implementation["getFeature"], undefined)
 }, "DOMImplementation.getFeature() must be nuked.")
 
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-manual.html
index 621b330..a3ac2ff 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-manual.html
@@ -7,7 +7,7 @@
 <script>
 async_test(function(t)
 {
-    trusted_request(document.querySelector("div"));
+    trusted_request(t, document.querySelector("div"));
 
     document.addEventListener("fullscreenchange", t.step_func(function(event)
     {
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-timing-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-timing-manual.html
index f91eb7e2..d52b64e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-timing-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-timing-manual.html
@@ -7,7 +7,7 @@
 <script>
 async_test(t => {
   const div = document.querySelector('div');
-  trusted_request(div);
+  trusted_request(t, div);
 
   document.onfullscreenchange = t.step_func(() => {
     // We are now in fullscreen. Exit again.
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual.html
index c80aeb41..49f3141 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual.html
@@ -27,6 +27,6 @@
   });
   document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
-  trusted_request(div);
+  trusted_request(t, div);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual.html
index 8f0af41..acd24e1 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual.html
@@ -22,11 +22,11 @@
         });
     });
 
-    trusted_click(t.step_func(function()
+    trusted_click(t, function()
     {
         assert_equals(document.fullscreenElement, null, "fullscreenElement before requestFullscreen()");
         div.requestFullscreen();
         assert_equals(document.fullscreenElement, null, "fullscreenElement after requestFullscreen()");
-    }), document.body);
+    }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-onfullscreenchange-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-onfullscreenchange-manual.html
index 1d05ce0..7415c111a 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-onfullscreenchange-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-onfullscreenchange-manual.html
@@ -10,6 +10,6 @@
     var div = document.querySelector("div");
     assert_equals(document.onfullscreenchange, null, "initial onfullscreenchange");
     document.onfullscreenchange = t.step_func_done();
-    trusted_request(div);
+    trusted_request(t, div);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html
index a630fe2..66f9968 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html
@@ -10,10 +10,10 @@
 async_test(function(t)
 {
     var iframes = document.getElementsByTagName("iframe");
-    trusted_request(iframes[0].contentDocument.body, document.body);
+    trusted_request(t, iframes[0].contentDocument.body, document.body);
     iframes[0].contentDocument.onfullscreenchange = t.step_func(function()
     {
-        trusted_request(iframes[1].contentDocument.body, iframes[0].contentDocument.body);
+        trusted_request(t, iframes[1].contentDocument.body, iframes[0].contentDocument.body);
         iframes[1].contentDocument.onfullscreenchange = t.unreached_func("fullscreenchange event");
         iframes[1].contentDocument.onfullscreenerror = t.step_func_done();
     });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-enabled-flag-not-set-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-enabled-flag-not-set-manual.html
index 501767d..1e18be5e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-enabled-flag-not-set-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-enabled-flag-not-set-manual.html
@@ -14,6 +14,6 @@
     iframe.contentDocument.onfullscreenchange = t.unreached_func("iframe fullscreenchange event");
     iframe.contentDocument.onfullscreenerror = t.step_func_done();
     assert_false(iframe.contentDocument.fullscreenEnabled, "fullscreen enabled flag");
-    trusted_request(iframe.contentDocument.body, document.body);
+    trusted_request(t, iframe.contentDocument.body, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-element-sibling-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-element-sibling-manual.html
index 0f79cb6..dc0c28c 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-element-sibling-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-element-sibling-manual.html
@@ -14,10 +14,10 @@
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, a, "fullscreen element");
-        trusted_request(b, a);
+        trusted_request(t, b, a);
         document.onfullscreenchange = t.unreached_func("second fullscreenchange event");
         document.onfullscreenerror = t.step_func_done();
     });
-    trusted_request(a);
+    trusted_request(t, a);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-iframe-child-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-iframe-child-manual.html
index 08febbd4..2563e70 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-iframe-child-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-fullscreen-iframe-child-manual.html
@@ -25,9 +25,9 @@
     });
     document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
-    trusted_request(div, iframe.contentDocument.body);
+    trusted_request(t, div, iframe.contentDocument.body);
   });
 
-  trusted_request(iframe);
+  trusted_request(t, iframe);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-iframe-child-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-iframe-child-manual.html
index ab1c261..b8f4f2d 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-iframe-child-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-iframe-child-manual.html
@@ -15,6 +15,6 @@
   });
   document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
-  trusted_request(div, document.body);
+  trusted_request(t, div, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-not-in-document-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-not-in-document-manual.html
index 904d3199..498e71db 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-not-in-document-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-ready-check-not-in-document-manual.html
@@ -10,6 +10,6 @@
     var div = document.createElement("div");
     document.onfullscreenchange = t.unreached_func("fullscreenchange event");
     document.onfullscreenerror = t.step_func_done();
-    trusted_request(div, document.body);
+    trusted_request(t, div, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-exit-iframe-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-exit-iframe-manual.html
index 4483cca..8305fc5e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-exit-iframe-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-exit-iframe-manual.html
@@ -33,6 +33,6 @@
   document.onfullscreenerror = t.unreached_func('fullscreenerror event');
   iframeDoc.onfullscreenerror = t.unreached_func('iframe fullscreenerror event');
 
-  trusted_request(iframeBody, document.body);
+  trusted_request(t, iframeBody, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual.html
index 697a27f3..c09cebb 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual.html
@@ -17,9 +17,9 @@
   });
   document.onfullscreenerror = t.unreached_func("fullscreenchange event");
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     target.requestFullscreen();
     moveTo.appendChild(target);
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual.html
index 255623e..28832824 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual.html
@@ -19,9 +19,9 @@
     assert_equals(iframeDoc.fullscreenElement, null);
   });
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     target.requestFullscreen();
     iframeDoc.body.appendChild(target);
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual.html
index e2149fd..198ae73 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual.html
@@ -15,7 +15,7 @@
   iframeDocument.onfullscreenchange = t.unreached_func("iframe fullscreenchange event");
   iframeDocument.onfullscreenerror = t.unreached_func("iframe fullscreenerror event");
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     iframeDocument.body.requestFullscreen();
     iframe.remove();
     // No events will be fired, end test after 100ms.
@@ -23,6 +23,6 @@
       assert_equals(document.fullscreenElement, null);
       assert_equals(iframeDocument.fullscreenElement, null);
     }), 100);
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html
index c754bf00..03c38cc 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual.html
@@ -14,9 +14,9 @@
     assert_equals(document.fullscreenElement, null);
   });
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     target.requestFullscreen();
     target.remove();
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-manual.html
index 43d1ea4..12f0a32 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-manual.html
@@ -17,6 +17,6 @@
         t.done();
     }));
 
-    trusted_request(div);
+    trusted_request(t, div);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-non-top-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-non-top-manual.html
index ed5e1c8..06c4c1b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-non-top-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-non-top-manual.html
@@ -11,16 +11,16 @@
 async_test(function(t)
 {
     var first = document.getElementById("first");
-    trusted_request(first);
+    trusted_request(t, first);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, first);
         var last = document.getElementById("last");
-        trusted_request(last);
+        trusted_request(t, last);
         document.onfullscreenchange = t.step_func(function()
         {
             assert_equals(document.fullscreenElement, last);
-            trusted_request(first, last);
+            trusted_request(t, first, last);
             document.onfullscreenerror = t.step_func_done();
         });
     });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-same-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-same-manual.html
index a475c18..defe9ce5 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-same-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-same-manual.html
@@ -16,15 +16,15 @@
     // doc's fullscreen element, terminate these subsubsteps."
     document.onfullscreenchange = t.unreached_func("fullscreenchange event");
 
-    trusted_click(t.step_func(() => {
+    trusted_click(t, () => {
       target.requestFullscreen();
 
       // Wait until after the next animation frame.
       requestAnimationFrame(t.step_func_done());
-    }), target);
+    }, target);
   });
   document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
-  trusted_request(target);
+  trusted_request(t, target);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-rect-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-rect-manual.html
index 9244d74e..77ea2b2 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-rect-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-rect-manual.html
@@ -10,7 +10,7 @@
 {
     var rect = document.querySelector("rect");
     assert_true(rect instanceof SVGRectElement);
-    trusted_request(rect, document.body);
+    trusted_request(t, rect, document.body);
     document.onfullscreenchange = t.unreached_func("fullscreenerror event");
     document.onfullscreenerror = t.step_func_done();
 });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-svg-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-svg-manual.html
index f971ee4d..c6e473c 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-svg-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-svg-svg-manual.html
@@ -10,7 +10,7 @@
 {
     var svg = document.querySelector("svg");
     assert_true(svg instanceof SVGSVGElement);
-    trusted_request(svg, document.body);
+    trusted_request(t, svg, document.body);
     document.onfullscreenchange = t.step_func_done();
     document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html
index f92b4f6..14fe5f70 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html
@@ -6,7 +6,7 @@
 <div id="log"></div>
 <script>
 async_test(t => {
-  trusted_request(document.querySelector('div'));
+  trusted_request(t, document.querySelector('div'));
 
   // If fullscreenchange is an animation frame event, then animation frame
   // callbacks should be run after it is fired, before the timer callback.
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-top-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-top-manual.html
index 4982916..eeeb783 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-top-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-top-manual.html
@@ -9,12 +9,12 @@
 async_test(function(t)
 {
     var top = document.getElementById("top");
-    trusted_request(top);
+    trusted_request(t, top);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, top);
         document.onfullscreenchange = t.unreached_func("fullscreenchange event");
-        trusted_click(t.step_func(function()
+        trusted_click(t, function()
         {
             top.requestFullscreen();
             // A fullscreenerror event would be fired after an async section
@@ -23,7 +23,7 @@
             {
                 requestAnimationFrame(t.step_func_done());
             }, 0);
-        }), top);
+        }, top);
     });
     document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 });
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual.html
index 4e843c8e..2b28983b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual.html
@@ -16,12 +16,12 @@
   });
   document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     // Request fullscreen twice.
     div.requestFullscreen();
     assert_equals(document.fullscreenElement, null, "fullscreenElement after first requestFullscreen()");
     div.requestFullscreen();
     assert_equals(document.fullscreenElement, null, "fullscreenElement after second requestFullscreen()");
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-elements-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-elements-manual.html
index 1ca4355..9611c816 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-elements-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-elements-manual.html
@@ -26,9 +26,9 @@
   });
   document.onfullscreenerror = t.unreached_func('fullscreenerror event');
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     b.requestFullscreen();
     a.requestFullscreen();
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-iframes-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-iframes-manual.html
index 60a880ef..99a7672e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-iframes-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-two-iframes-manual.html
@@ -28,9 +28,9 @@
   });
   document.onfullscreenerror = t.unreached_func('fullscreenerror event');
 
-  trusted_click(t.step_func(() => {
+  trusted_click(t, () => {
     b.contentDocument.body.requestFullscreen();
     a.contentDocument.body.requestFullscreen();
-  }), document.body);
+  }, document.body);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-child-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-child-manual.html
index 63cc727f..4c33733 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-child-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-child-manual.html
@@ -11,7 +11,7 @@
 async_test(function(t)
 {
     var parent = document.getElementById("parent");
-    trusted_request(parent);
+    trusted_request(t, parent);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, parent);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-first-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-first-manual.html
index 5873a1cf..861dc69 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-first-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-first-manual.html
@@ -11,12 +11,12 @@
 async_test(function(t)
 {
     var first = document.getElementById("first");
-    trusted_request(first);
+    trusted_request(t, first);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, first);
         var last = document.getElementById("last");
-        trusted_request(last);
+        trusted_request(t, last);
         document.onfullscreenchange = t.step_func(function()
         {
             assert_equals(document.fullscreenElement, last);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-last-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-last-manual.html
index 3e52049..3837439 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-last-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-last-manual.html
@@ -11,12 +11,12 @@
 async_test(function(t)
 {
     var first = document.getElementById("first");
-    trusted_request(first);
+    trusted_request(t, first);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, first);
         var last = document.getElementById("last");
-        trusted_request(last);
+        trusted_request(t, last);
         document.onfullscreenchange = t.step_func(function()
         {
             assert_equals(document.fullscreenElement, last);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-parent-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-parent-manual.html
index 74327637..a284fc6 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-parent-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-parent-manual.html
@@ -11,7 +11,7 @@
 async_test(function(t)
 {
     var child = document.getElementById("child");
-    trusted_request(child);
+    trusted_request(t, child);
     document.onfullscreenchange = t.step_func(function()
     {
         assert_equals(document.fullscreenElement, child);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-single-manual.html b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-single-manual.html
index 53abb5e8..9a57d93 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-single-manual.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/model/remove-single-manual.html
@@ -19,6 +19,6 @@
             t.done();
         });
     });
-    trusted_request(single);
+    trusted_request(t, single);
 });
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/trusted-click.js b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/trusted-click.js
index 6cd4020..cab23e3 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/trusted-click.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/trusted-click.js
@@ -1,6 +1,6 @@
 // Invokes callback from a trusted click event, to satisfy
 // https://html.spec.whatwg.org/#triggered-by-user-activation
-function trusted_click(callback, container)
+function trusted_click(test, callback, container)
 {
     var document = container.ownerDocument;
     var button = document.createElement("button");
@@ -8,17 +8,16 @@
     button.style.display = "block";
     button.style.fontSize = "20px";
     button.style.padding = "10px";
-    button.onclick = function()
+    button.onclick = test.step_func(function()
     {
         callback();
         container.removeChild(button);
-    };
+    });
     container.appendChild(button);
 }
 
 // Invokes element.requestFullscreen() from a trusted click.
-function trusted_request(element, container)
+function trusted_request(test, element, container)
 {
-    var request = element.requestFullscreen.bind(element);
-    trusted_click(request, container || element.parentNode);
+    trusted_click(test, () => element.requestFullscreen(), container || element.parentNode);
 }
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html
new file mode 100644
index 0000000..1e9b10d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resources/post_name_on_load.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+addEventListener('load', _ => {
+  let params = new URLSearchParams(window.location.search);
+  window.opener.postMessage(params.get('name'), '*');
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html
new file mode 100644
index 0000000..fb7365b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/resume-timer-on-history-back.html
@@ -0,0 +1,145 @@
+<!doctype html>
+<title>Verify history.back() on a persisted page resumes timers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="text/javascript">
+
+function make_post_back_url(name) {
+  return new URL('resources/post_name_on_load.html?name=' + name,
+                 window.location).href;
+}
+
+function wait_for_message(name) {
+  return new Promise(resolve => {
+    addEventListener('message', function onMsg(evt) {
+      if (evt.data !== name) {
+        return;
+      }
+      removeEventListener('message', onMsg);
+      resolve();
+    });
+  });
+}
+
+function with_window_by_name(name) {
+  let win = window.open(make_post_back_url(name));
+  return wait_for_message(name).then(_ => {
+    return win;
+  });
+}
+
+function with_nested_frame(win, url) {
+  return new Promise(resolve => {
+    let frame = win.document.createElement('iframe');
+    frame.addEventListener('load', function onLoad(evt) {
+      removeEventListener('load', onLoad);
+      resolve(frame);
+    });
+    frame.src = url;
+    win.document.body.appendChild(frame);
+  });
+}
+
+function delay(win, delay) {
+  return new Promise(resolve => {
+    win.setTimeout(_ => {
+      resolve(win);
+    }, delay);
+  });
+}
+
+function navigate_by_name(win, name) {
+  win.location = make_post_back_url(name);
+  return wait_for_message(name).then(_ => {
+    return win;
+  });
+}
+
+function go_back(win) {
+  return new Promise(resolve => {
+    win.onpagehide = e => resolve(win);
+    win.history.back();
+  });
+}
+
+let DELAY = 500;
+
+promise_test(t => {
+  // Create a new window so we can navigate it later.
+  return with_window_by_name('foo').then(win => {
+    // Schedule a timer within the new window.  Our intent is
+    // to navigate the window before the timer fires.
+    let delayFired = false;
+    let innerDelay = delay(win, DELAY);
+    innerDelay.then(_ => {
+      delayFired = true;
+    });
+
+    return navigate_by_name(win, 'bar').then(_ => {
+      // Since the window has navigated the timer should not
+      // fire.  We set a timer on our current test window
+      // to verify the other timer is not received.
+      assert_false(delayFired);
+      return delay(window, DELAY * 2);
+    }).then(_ => {
+      // The navigated window's timer should not have fired.
+      assert_false(delayFired);
+      // Now go back to the document that set the timer.
+      return go_back(win);
+    }).then(_ => {
+      // We wait for one of two conditions here.  For browsers
+      // with a bfcache the original suspended timer will fire.
+      // Alternatively, if the browser reloads the page the original
+      // message will be sent again.  Wait for either of these
+      // two events.
+      return Promise.race([wait_for_message('foo'), innerDelay]);
+    }).then(_ => {
+      win.close();
+    });
+  });
+}, 'history.back() handles top level page timer correctly');
+
+promise_test(t => {
+  let win;
+  // Create a new window so we can navigate it later.
+  return with_window_by_name('foo').then(w => {
+    win = w;
+
+    // Create a nested frame so we check if navigation and history.back()
+    // properly handle child window state.
+    return with_nested_frame(win, 'about:blank');
+
+  }).then(frame => {
+    // Schedule a timer within the nested frame contained by the new window.
+    // Our intent is to navigate the window before the timer fires.
+    let delayFired = false;
+    let innerDelay = delay(frame.contentWindow, DELAY);
+    innerDelay.then(_ => {
+      delayFired = true;
+    });
+
+    return navigate_by_name(win, 'bar').then(_ => {
+      // Since the window has navigated the timer should not
+      // fire.  We set a timer on our current test window
+      // to verify the other timer is not received.
+      assert_false(delayFired);
+      return delay(window, DELAY * 2);
+    }).then(_ => {
+      // The navigated window's timer should not have fired.
+      assert_false(delayFired);
+      // Now go back to the document containing the frame that set the timer.
+      return go_back(win);
+    }).then(_ => {
+      // We wait for one of two conditions here.  For browsers
+      // with a bfcache the original suspended timer will fire.
+      // Alternatively, if the browser reloads the page the original
+      // message will be sent again.  Wait for either of these
+      // two events.
+      return Promise.race([wait_for_message('foo'), innerDelay]);
+    }).then(_ => {
+      win.close();
+    });
+  });
+}, 'history.back() handles nested iframe timer correctly');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html
new file mode 100644
index 0000000..0954602
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/history/the-location-interface/location-pathname-setter-question-mark.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>Set location.pathname to ?</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<iframe src=/common/blank.html></iframe>
+<script>
+async_test((t) => {
+  onload = t.step_func(() => {
+    self[0].frameElement.onload = t.step_func_done(() => {
+      assert_equals(self[0].location.pathname, "/%3F")
+    })
+    self[0].location.pathname = "?"
+  })
+})
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
index 79440e2..7560fcd 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -70,16 +70,25 @@
  * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior.
  */
 
-var whitelistedWindowProps = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
-                              'opener', 'closed', 'close', 'blur', 'focus', 'length'];
+var whitelistedWindowIndices = ['0', '1'];
+var whitelistedWindowPropNames = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
+                                  'opener', 'closed', 'close', 'blur', 'focus', 'length'];
+whitelistedWindowPropNames = whitelistedWindowPropNames.concat(whitelistedWindowIndices);
+whitelistedWindowPropNames.sort();
+var whitelistedLocationPropNames = ['href', 'replace'];
+whitelistedLocationPropNames.sort();
+var whitelistedSymbols = [Symbol.toStringTag, Symbol.hasInstance,
+                          Symbol.isConcatSpreadable];
+var whitelistedWindowProps = whitelistedWindowPropNames.concat(whitelistedSymbols);
+
 addTest(function() {
   for (var prop in window) {
     if (whitelistedWindowProps.indexOf(prop) != -1) {
       C[prop]; // Shouldn't throw.
       Object.getOwnPropertyDescriptor(C, prop); // Shouldn't throw.
-      assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + prop);
+      assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + String(prop));
     } else {
-      assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + prop + " on Window");
+      assert_throws(null, function() { C[prop]; }, "Should throw when accessing " + String(prop) + " on Window");
       assert_throws(null, function() { Object.getOwnPropertyDescriptor(C, prop); },
                     "Should throw when accessing property descriptor for " + prop + " on Window");
       assert_throws(null, function() { Object.prototype.hasOwnProperty.call(C, prop); },
@@ -169,9 +178,18 @@
 }, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|");
 
 function checkPropertyDescriptor(desc, propName, expectWritable) {
+  var isSymbol = (typeof(propName) == "symbol");
+  propName = String(propName);
   assert_true(isObject(desc), "property descriptor for " + propName + " should exist");
   assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should be non-enumerable");
   assert_equals(desc.configurable, true, "property descriptor for " + propName + " should be configurable");
+  if (isSymbol) {
+    assert_true("value" in desc,
+                "property descriptor for " + propName + " should be a value descriptor");
+    assert_equals(desc.value, undefined,
+                  "symbol-named cross-origin visible prop " + propName +
+                  " should come back as undefined");
+  }
   if ('value' in desc)
     assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable);
   else
@@ -187,6 +205,10 @@
   checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'replace'), 'replace', false);
   checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'href'), 'href', true);
   assert_equals(typeof Object.getOwnPropertyDescriptor(C.location, 'href').get, 'undefined', "Cross-origin location should have no href getter");
+  whitelistedSymbols.forEach(function(prop) {
+    var desc = Object.getOwnPropertyDescriptor(C.location, prop);
+    checkPropertyDescriptor(desc, prop, false);
+  });
 }, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
 
 /*
@@ -243,13 +265,44 @@
  */
 
 addTest(function() {
-  assert_array_equals(whitelistedWindowProps.sort(), Object.getOwnPropertyNames(C).sort(),
+  assert_array_equals(Object.getOwnPropertyNames(C).sort(),
+                      whitelistedWindowPropNames,
                       "Object.getOwnPropertyNames() gives the right answer for cross-origin Window");
-  assert_array_equals(Object.getOwnPropertyNames(C.location).sort(), ['href', 'replace'],
+  assert_array_equals(Object.getOwnPropertyNames(C.location).sort(),
+                      whitelistedLocationPropNames,
                       "Object.getOwnPropertyNames() gives the right answer for cross-origin Location");
 }, "[[OwnPropertyKeys]] should return all properties from cross-origin objects");
 
 addTest(function() {
+  assert_array_equals(Object.getOwnPropertySymbols(C), whitelistedSymbols,
+    "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window");
+  assert_array_equals(Object.getOwnPropertySymbols(C.location),
+                      whitelistedSymbols,
+    "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Location");
+}, "[[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects");
+
+addTest(function() {
+  var allWindowProps = Reflect.ownKeys(C);
+  indexedWindowProps = allWindowProps.slice(0, whitelistedWindowIndices.length);
+  stringWindowProps = allWindowProps.slice(0, -1 * whitelistedSymbols.length);
+  symbolWindowProps = allWindowProps.slice(-1 * whitelistedSymbols.length);
+  assert_array_equals(indexedWindowProps, whitelistedWindowIndices,
+                      "Reflect.ownKeys should start with the indices exposed on the cross-origin window.");
+  assert_array_equals(stringWindowProps.sort(), whitelistedWindowPropNames,
+                      "Reflect.ownKeys should continue with the cross-origin window properties for a cross-origin Window.");
+  assert_array_equals(symbolWindowProps, whitelistedSymbols,
+                      "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Window.");
+
+  var allLocationProps = Reflect.ownKeys(C.location);
+  stringLocationProps = allLocationProps.slice(0, -1 * whitelistedSymbols.length);
+  symbolLocationProps = allLocationProps.slice(-1 * whitelistedSymbols.length);
+  assert_array_equals(stringLocationProps.sort(), whitelistedLocationPropNames,
+                      "Reflect.ownKeys should start with the cross-origin window properties for a cross-origin Location.")
+  assert_array_equals(symbolLocationProps, whitelistedSymbols,
+                      "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Location.")
+}, "[[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices");
+
+addTest(function() {
   assert_true(B.eval('parent.C') === C, "A and B observe the same identity for C's Window");
   assert_true(B.eval('parent.C.location') === C.location, "A and B observe the same identity for C's Location");
 }, "A and B jointly observe the same identity for cross-origin Window and Location");
@@ -312,6 +365,11 @@
   checkFunction(set_href_B, B.Function.prototype);
 }, "Same-origin observers get different accessors for cross-origin Location");
 
+addTest(function() {
+  assert_equals({}.toString.call(C), "[object Object]");
+  assert_equals({}.toString.call(C.location), "[object Object]");
+}, "{}.toString.call() does the right thing on cross-origin objects");
+
 // We do a fresh load of the subframes for each test to minimize side-effects.
 // It would be nice to reload ourselves as well, but we can't do that without
 // disrupting the test harness.
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/frame.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/frame.html
index 046e49da..0a7769d 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/frame.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/frame.html
@@ -35,5 +35,8 @@
 </script>
 </head>
 <body>
+  <!-- Two subframes to give us some indexed properties -->
+  <iframe></iframe>
+  <iframe></iframe>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html
index 3bfcd0c..a315e21 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <script src="/common/get-host-info.sub.js"></script>
   <script>
     function loadFrames() {
       window.A = document.getElementById('A').contentWindow;
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/dom/elements/elements-in-the-dom/historical.html b/third_party/WebKit/LayoutTests/imported/wpt/html/dom/elements/elements-in-the-dom/historical.html
new file mode 100644
index 0000000..c18ee31
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/dom/elements/elements-in-the-dom/historical.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Historical HTMLElement features</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+[
+  // https://github.com/whatwg/html/commit/389ec2620d89e9480ef8847bf016abdfa92427bc
+  "commandType",
+  "commandLabel",
+  "commandIcon",
+  "commandHidden",
+  "commandDisabled",
+  "commandChecked",
+  "commandTriggers",
+  // https://github.com/whatwg/html/commit/5ddfc78b1f82e86cc202d72ccc752a0e15f1e4ad
+  "inert",
+].forEach(function(member) {
+  test(function() {
+    assert_false(member in document.body);
+    assert_false(member in document.createElement('div'));
+  }, 'HTMLElement member must be nuked: ' + member);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/image.png b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/image.png
new file mode 100644
index 0000000..d26878c9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/image.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/update-src-complete.html b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/update-src-complete.html
new file mode 100644
index 0000000..267c0082
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/embedded-content/the-img-element/update-src-complete.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Changing the img src should retain the 'complete' property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p id="display"><img src="image.png"></p>
+<script>
+    function check() {
+        var img = document.querySelector("img");
+        assert_true(img.complete, "By onload, image should have loaded");
+        img.src = `image.png?${Math.random()}`;
+        assert_false(img.complete, "Now that we're loading we should no longer be complete");
+        img.onload = function () {
+            assert_true(img.complete, "The new thing should have loaded.");
+            done();
+        }
+    }
+
+    onload = function () {
+        check();
+    };
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-form-element/form-nameditem.html b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-form-element/form-nameditem.html
index 3edf903b..0155d38b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-form-element/form-nameditem.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-form-element/form-nameditem.html
@@ -327,4 +327,93 @@
   assert_equals(form["new-name2"], 5);
 }, 'Trying to set a non-configurable expando that shadows a named property that gets added later');
 
+test(function() {
+  var form = document.getElementsByTagName("form")[1];
+
+  var i1 = document.createElement("input");
+  i1.name = "past-name1";
+  i1.id = "past-id1"
+
+  assert_equals(form["past-name1"], undefined);
+  assert_equals(form["past-id1"], undefined);
+  form.appendChild(i1);
+  assert_equals(form["past-name1"], i1);
+  assert_equals(form["past-id1"], i1);
+
+  i1.name = "twiddled-name1";
+  i1.id = "twiddled-id1";
+  assert_equals(form["past-name1"], i1);
+  assert_equals(form["twiddled-name1"], i1);
+  assert_equals(form["past-id1"], i1);
+  assert_equals(form["twiddled-id1"], i1);
+
+  i1.name = "twiddled-name2";
+  i1.id = "twiddled-id2";
+  assert_equals(form["past-name1"], i1);
+  assert_equals(form["twiddled-name1"], i1);
+  assert_equals(form["twiddled-name2"], i1);
+  assert_equals(form["past-id1"], i1);
+  assert_equals(form["twiddled-id1"], i1);
+  assert_equals(form["twiddled-id2"], i1);
+
+  i1.removeAttribute("id");
+  i1.removeAttribute("name");
+  assert_equals(form["past-name1"], i1);
+  assert_equals(form["twiddled-name1"], i1);
+  assert_equals(form["twiddled-name2"], i1);
+  assert_equals(form["past-id1"], i1);
+  assert_equals(form["twiddled-id1"], i1);
+  assert_equals(form["twiddled-id2"], i1);
+
+  i1.remove();
+  assert_equals(form["past-name1"], undefined);
+  assert_equals(form["twiddled-name1"], undefined);
+  assert_equals(form["twiddled-name2"], undefined);
+  assert_equals(form["past-id1"], undefined);
+  assert_equals(form["twiddled-id1"], undefined);
+  assert_equals(form["twiddled-id2"], undefined);
+
+  var i2 = document.createElement("input");
+  i2.name = "past-name2";
+  i2.id = "past-id2";
+
+  assert_equals(form["past-name2"], undefined);
+  assert_equals(form["past-id2"], undefined);
+  form.appendChild(i2);
+  assert_equals(form["past-name2"], i2);
+  assert_equals(form["past-id2"], i2);
+
+  i2.name = "twiddled-name3";
+  i2.id = "twiddled-id3";
+  assert_equals(form["past-name2"], i2);
+  assert_equals(form["twiddled-name3"], i2);
+  assert_equals(form["past-id2"], i2);
+  assert_equals(form["twiddled-id3"], i2);
+
+  i2.name = "twiddled-name4";
+  i2.id = "twiddled-id4";
+  assert_equals(form["past-name2"], i2);
+  assert_equals(form["twiddled-name3"], i2);
+  assert_equals(form["twiddled-name4"], i2);
+  assert_equals(form["past-id2"], i2);
+  assert_equals(form["twiddled-id3"], i2);
+  assert_equals(form["twiddled-id4"], i2);
+
+  i2.removeAttribute("id");
+  i2.removeAttribute("name");
+  assert_equals(form["past-name2"], i2);
+  assert_equals(form["twiddled-name3"], i2);
+  assert_equals(form["twiddled-name4"], i2);
+  assert_equals(form["past-id2"], i2);
+  assert_equals(form["twiddled-id3"], i2);
+  assert_equals(form["twiddled-id4"], i2);
+
+  i2.setAttribute("form", "c");
+  assert_equals(form["past-name2"], undefined);
+  assert_equals(form["twiddled-name3"], undefined);
+  assert_equals(form["twiddled-name4"], undefined);
+  assert_equals(form["past-id2"], undefined);
+  assert_equals(form["twiddled-id3"], undefined);
+  assert_equals(form["twiddled-id4"], undefined);
+}, "Past names map should work correctly");
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-input-element/clone.html b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-input-element/clone.html
new file mode 100644
index 0000000..0f7e053b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-input-element/clone.html
@@ -0,0 +1,150 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test input value retention upon clone</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>form {display: none;} </style>
+<form>
+<p><input type=checkbox> This checkbox is initially unchecked.</p>
+<p><input type=checkbox checked="checked"> This checkbox is initially checked.</p>
+<p><input type=radio name=radio> This radiobutton is initially unchecked.</p>
+<p><input type=radio checked="checked" name=radio> This radiobutton is initially checked.</p>
+<p><input type=hidden value="DEFAULT
+DEFAULT"> This hidden field has the initial value "DEFAULT\nDEFAULT".</p>
+<p><input type=text value=DEFAULT> This text field has the initial value "DEFAULT".</p>
+<p><input type=search value=DEFAULT> This search field has the initial value "DEFAULT".</p>
+<p><input type=tel value=DEFAULT> This phone number field has the initial value "DEFAULT".</p>
+<p><input type=url value=https://default.invalid/> This URL field has the initial value "https://default.invalid/".</p>
+<p><input type=email value=default@default.invalid> This email field has the initial value "default@default.invalid".</p>
+<p><input type=password value=DEFAULT> This password field has the initial value "DEFAULT".</p>
+<p><input type=date value=2015-01-01> This date field has the initial value "2015-01-01".</p>
+<p><input type=month value=2015-01> This month field has the initial value "2015-01".</p>
+<p><input type=week value=2015-W01> This week field has the initial value "2015-W01".</p>
+<p><input type=time value=12:00> This time field has the initial value "12:00".</p>
+<p><input type=datetime-local value=2015-01-01T12:00> This datetime (local) field has the initial value "2015-01-01T12:00".</p>
+<p><input type=number value=1> This number field has the initial value "1".</p>
+<p><input type=range value=1> This range control has the initial value "1".</p>
+<p><input type=color value=#ff0000> This color picker has the initial value "#FF0000".</p>
+<p><input type="button" value="Clone" onclick="clone();"></p>
+</form>
+<script>
+setup(function() {
+    let form = document.getElementsByTagName("form")[0];
+    let inputs = form.getElementsByTagName("input");
+    inputs[0].checked = true;
+    inputs[1].checked = false;
+    inputs[2].checked = true;
+    inputs[4].value = "CHANGED\nCHANGED";
+    inputs[5].value = "CHANGED";
+    inputs[6].value = "CHANGED";
+    inputs[7].value = "CHANGED";
+    inputs[8].value = "https://changed.invalid/";
+    inputs[9].value = "changed@changed.invalid";
+    inputs[10].value = "CHANGED";
+    inputs[11].value = "2016-01-01";
+    inputs[12].value = "2016-01";
+    inputs[13].value = "2016-W01";
+    inputs[14].value = "12:30";
+    inputs[15].value = "2016-01-01T12:30";
+    inputs[16].value = "2";
+    inputs[17].value = "2";
+    inputs[18].value = "#00ff00";
+    let clone = form.cloneNode(true);
+    document.body.appendChild(clone);
+});
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_true(inputs[0].checked, "Should have retained checked state");
+}, "Checkbox must retain checked state.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_false(inputs[1].checked, "Should have retained unchecked state");
+}, "Checkbox must retain unchecked state.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_true(inputs[2].checked, "Should have retained checked state");
+}, "Radiobutton must retain checked state.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_false(inputs[3].checked, "Should have retained unchecked state");
+}, "Radiobutton must retain unchecked state.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[4].value, "CHANGED\nCHANGED", "Should have retained the changed value.");
+}, "Hidden field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[5].value, "CHANGED", "Should have retained the changed value.");
+}, "Text field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[6].value, "CHANGED", "Should have retained the changed value.");
+}, "Search field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[7].value, "CHANGED", "Should have retained the changed value.");
+}, "Phone number field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[8].value, "https://changed.invalid/", "Should have retained the changed value.");
+}, "URL field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[9].value, "changed@changed.invalid", "Should have retained the changed value.");
+}, "Email field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[10].value, "CHANGED", "Should have retained the changed value.");
+}, "Password field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[11].value, "2016-01-01", "Should have retained the changed value.");
+}, "Date field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[12].value, "2016-01", "Should have retained the changed value.");
+}, "Month field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[13].value, "2016-W01", "Should have retained the changed value.");
+}, "Week field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[14].value, "12:30", "Should have retained the changed value.");
+}, "Time field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[15].value, "2016-01-01T12:30", "Should have retained the changed value.");
+}, "Datetime (local) field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[16].value, "2", "Should have retained the changed value.");
+}, "Number field must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[17].value, "2", "Should have retained the changed value.");
+}, "Range control must retain changed value.");
+test(function() {
+    let clone = document.getElementsByTagName("form")[1];
+    let inputs = clone.getElementsByTagName("input");
+    assert_equals(inputs[18].value, "#00ff00", "Should have retained the changed value.");
+}, "Color picker must retain changed value.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-select-element/select-validity.html b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-select-element/select-validity.html
new file mode 100644
index 0000000..73f41df
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/forms/the-select-element/select-validity.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLSelectElement.checkValidity</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#the-select-element:attr-select-required-4">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+
+test(function() {
+  var select = document.createElement('select');
+  assert_true(select.willValidate, "A select element is a submittable element that is a candidate for constraint validation.");
+  var placeholder = document.createElement('option');
+  select.appendChild(placeholder);
+  assert_true(select.checkValidity(), "Always valid when the select isn't a required value.");
+  select.required = true;
+  assert_true(placeholder.selected, "If display size is 1, multiple is absent and no options have selectedness true, the first option is selected.");
+  assert_equals(select.value, "", "The placeholder's value should be the select's value right now");
+  assert_false(select.checkValidity(), "A selected placeholder option should invalidate the select.");
+  var emptyOption = document.createElement('option');
+  select.appendChild(emptyOption);
+  emptyOption.selected = true;
+  assert_equals(select.value, "", "The empty value should be set.");
+  assert_true(select.checkValidity(), "An empty non-placeholder option should be a valid choice.");
+  var filledOption = document.createElement('option');
+  filledOption.value = "test";
+  select.appendChild(filledOption);
+  filledOption.selected = true;
+  assert_equals(select.value, "test", "The non-empty value should be set.");
+  assert_true(select.checkValidity(), "A non-empty non-placeholder option should be a valid choice.");
+  select.removeChild(placeholder);
+  select.appendChild(emptyOption); // move emptyOption to second place
+  emptyOption.selected = true;
+  assert_equals(select.value, "", "The empty value should be set.");
+  assert_true(select.checkValidity(), "Only the first option can be seen as a placeholder.");
+  placeholder.disabled = true;
+  select.insertBefore(placeholder, filledOption);
+  placeholder.selected = true;
+  assert_equals(select.value, "", "A disabled first placeholder option should result in an empty value.");
+  assert_false(select.checkValidity(), "A disabled first placeholder option should invalidate the select.");
+}, "Placeholder label options within a select");
+
+test(function() {
+  var select = document.createElement('select');
+  select.required = true;
+  var optgroup = document.createElement('optgroup');
+  var emptyOption = document.createElement('option');
+  optgroup.appendChild(emptyOption);
+  select.appendChild(optgroup);
+  emptyOption.selected = true;
+  assert_equals(select.value, "", "The empty value should be set.");
+  assert_true(select.checkValidity(), "The first option is not considered a placeholder if it is located within an optgroup.");
+  var otherEmptyOption = document.createElement('option');
+  otherEmptyOption.value = "";
+  select.appendChild(otherEmptyOption);
+  otherEmptyOption.selected = true;
+  assert_equals(select.value, "", "The empty value should be set.");
+  assert_true(select.checkValidity(), "The empty option should be accepted as it is not the first option in the tree ordered list.");
+}, "Placeholder label-like options within optgroup");
+
+test(function() {
+  var select = document.createElement('select');
+  select.required = true;
+  select.size = 2;
+  var emptyOption = document.createElement('option');
+  select.appendChild(emptyOption);
+  assert_false(emptyOption.selected, "Display size is not 1, so the first option should not be selected.");
+  assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+  emptyOption.selected = true;
+  assert_true(select.checkValidity(), "If one option is selected, the select should be considered valid.");
+  var otherEmptyOption = document.createElement('option');
+  otherEmptyOption.value = "";
+  select.appendChild(otherEmptyOption);
+  otherEmptyOption.selected = true;
+  assert_false(emptyOption.selected, "Whenever an option has its selectiveness set to true, the other options must be set to false.");
+  otherEmptyOption.selected = false;
+  assert_false(otherEmptyOption.selected, "It should be possible to set the selectiveness to false with a display size more than one.");
+  assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+}, "Validation on selects with display size set as more than one");
+
+test(function() {
+  var select = document.createElement('select');
+  select.required = true;
+  select.multiple = true;
+  var emptyOption = document.createElement('option');
+  select.appendChild(emptyOption);
+  assert_false(select.checkValidity(), "If no options are selected the select must be seen as invalid.");
+  emptyOption.selected = true;
+  assert_true(select.checkValidity(), "If one option is selected, the select should be considered valid.");
+  var optgroup = document.createElement('optgroup');
+  optgroup.appendChild(emptyOption); // Move option to optgroup
+  select.appendChild(optgroup);
+  assert_true(select.checkValidity(), "If one option within an optgroup or not is selected, the select should be considered valid.");
+}, "Validation on selects with multiple set");
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html
index 6ca9189..30b30e1 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-showModal.html
@@ -55,11 +55,9 @@
 
   test(function(){
     assert_false(d1.open);
-    assert_false(b0.commandDisabled);
     d1.showModal();
     this.add_cleanup(function() { d1.close(); });
     assert_true(d1.open);
-    assert_true(b0.commandDisabled);
     assert_equals(document.activeElement, b1);
   });
 
@@ -80,11 +78,8 @@
 
   test(function(){
     assert_false(d3.open);
-    assert_false(b3.commandDisabled);
     assert_false(d4.open);
-    assert_false(b4.commandDisabled);
     assert_false(d5.open);
-    assert_false(b5.commandDisabled);
     d3.showModal();
     this.add_cleanup(function() { d3.close(); });
     d4.showModal();
@@ -92,11 +87,8 @@
     d5.showModal();
     this.add_cleanup(function() { d5.close(); });
     assert_true(d3.open);
-    assert_true(b3.commandDisabled);
     assert_true(d4.open);
-    assert_true(b4.commandDisabled);
     assert_true(d5.open);
-    assert_false(b5.commandDisabled);
   }, "when opening multiple dialogs, only the newest one is non-inert");
 
   test(function(){
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-add.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-add.js
index c41c7bc..237ba47 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-add.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-add.js
@@ -84,6 +84,26 @@
   }, 'Cache.add with request with null body (not consumed)');
 
 cache_test(function(cache, test) {
+    return assert_promise_rejects(
+      test,
+      new TypeError(),
+      cache.add('../resources/fetch-status.py?status=206'),
+      'Cache.add should reject on partial response');
+  }, 'Cache.add with 206 response');
+
+cache_test(function(cache, test) {
+    var urls = ['../resources/fetch-status.py?status=206',
+                '../resources/fetch-status.py?status=200'];
+    var requests = urls.map(function(url) {
+        return new Request(url);
+      });
+    return promise_rejects(
+      new TypeError(),
+      cache.addAll(requests),
+      'Cache.addAll should reject with TypeError if any request fails');
+  }, 'Cache.addAll with 206 response');
+
+cache_test(function(cache, test) {
     return promise_rejects(
       test,
       new TypeError(),
@@ -91,6 +111,7 @@
       'Cache.add should reject if response is !ok');
   }, 'Cache.add with request that results in a status of 404');
 
+
 cache_test(function(cache, test) {
     return promise_rejects(
       test,
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-delete.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-delete.js
index 446818c..bf7841a 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-delete.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-delete.js
@@ -56,6 +56,25 @@
   }, 'Cache.delete called with a Request object');
 
 cache_test(function(cache) {
+    var request = new Request(test_url);
+    var response = new_test_response();
+    return cache.put(request, response)
+      .then(function() {
+          return cache.delete(new Request(test_url, {method: 'HEAD'}));
+        })
+      .then(function(result) {
+          assert_false(result,
+                       'Cache.delete should not match a non-GET request ' +
+                       'unless ignoreMethod option is set.');
+          return cache.match(test_url);
+        })
+      .then(function(result) {
+          assert_response_equals(result, response,
+            'Cache.delete should leave non-matching response in the cache.');
+        });
+  }, 'Cache.delete called with a HEAD request');
+
+cache_test(function(cache) {
     return cache.delete(test_url)
       .then(function(result) {
           assert_false(result,
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-match.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-match.js
index fbe564d..32c9ec9 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-match.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-match.js
@@ -36,6 +36,14 @@
   }, 'Cache.match with new Request');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
+    return cache.match(new Request(entries.a.request.url, {method: 'HEAD'}))
+      .then(function(result) {
+          assert_equals(result, undefined,
+                        'Cache.match should not match HEAD Request.');
+        });
+  }, 'Cache.match with HEAD');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.match(entries.a.request,
                        {ignoreSearch: true})
       .then(function(result) {
@@ -189,4 +197,28 @@
         });
   }, 'Cache.match with a network error Response');
 
+cache_test(function(cache) {
+    // This test validates that we can get a Response from the Cache API,
+    // clone it, and read just one side of the clone.  This was previously
+    // bugged in FF for Responses with large bodies.
+    var data = [];
+    data.length = 80 * 1024;
+    data.fill('F');
+    var response;
+    return cache.put('/', new Response(data.toString()))
+      .then(function(result) {
+          return cache.match('/');
+        })
+      .then(function(r) {
+          // Make sure the original response is not GC'd.
+          response = r;
+          // Return only the clone.  We purposefully test that the other
+          // half of the clone does not need to be read here.
+          return response.clone().text();
+        })
+      .then(function(text) {
+          assert_equals(text, data.toString(), 'cloned body text can be read correctly');
+        })
+  }, 'Cache produces large Responses that can be cloned and read correctly.');
+
 done();
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-matchAll.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-matchAll.js
index 5de9a5e..feace5a9 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-matchAll.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-matchAll.js
@@ -39,6 +39,16 @@
   }, 'Cache.matchAll with new Request');
 
 prepopulated_cache_test(simple_entries, function(cache, entries) {
+    return cache.matchAll(new Request(entries.a.request.url, {method: 'HEAD'}),
+                          {ignoreSearch: true})
+      .then(function(result) {
+          assert_response_array_equals(
+            result, [],
+            'Cache.matchAll should not match HEAD Request.');
+        });
+  }, 'Cache.matchAll with HEAD');
+
+prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.matchAll(entries.a.request,
                           {ignoreSearch: true})
       .then(function(result) {
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-put.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-put.js
index 71a0e4d9..9e9fd67a 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-put.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-put.js
@@ -103,6 +103,19 @@
   }, 'Cache.put with an empty response body');
 
 cache_test(function(cache) {
+    var request = new Request(test_url);
+    var response = new Response('', {
+        status: 206,
+        headers: [['Content-Type', 'text/plain']]
+      });
+
+    return assert_promise_rejects(
+      cache.put(request, response),
+      new TypeError(),
+      'Cache.put should reject 206 Responses with a TypeError.');
+  }, 'Cache.put with 206 response');
+
+cache_test(function(cache) {
     var test_url = new URL('../resources/fetch-status.py?status=500', location.href).href;
     var request = new Request(test_url);
     var response;
@@ -290,4 +303,22 @@
       'TypeError.');
   }, 'Cache.put with an embedded VARY:* Response');
 
+cache_test(function(cache) {
+    var url = 'foo.html';
+    var redirectURL = 'http://example.com/foo-bar.html';
+    var redirectResponse = Response.redirect(redirectURL);
+    assert_equals(redirectResponse.headers.get('Location'), redirectURL,
+                  'Response.redirect() should set Location header.');
+    return cache.put(url, redirectResponse.clone())
+      .then(function() {
+          return cache.match(url);
+        })
+      .then(function(response) {
+          assert_response_equals(response, redirectResponse,
+                                 'Redirect response is reproduced by the Cache API');
+          assert_equals(response.headers.get('Location'), redirectURL,
+                        'Location header is preserved by Cache API.');
+        });
+  }, 'Cache.put should store Response.redirect() correctly');
+
 done();
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-storage-match.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-storage-match.js
index 3c1cdd7..21517b1e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-storage-match.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/cache-storage/script-tests/cache-storage-match.js
@@ -93,6 +93,19 @@
         });
 }, 'CacheStorageMatch a string request');
 
+cache_test(function(cache) {
+    var transaction = create_unique_transaction();
+    return cache.put(transaction.request.clone(), transaction.response.clone())
+      .then(function() {
+          return self.caches.match(new Request(transaction.request.url,
+                                              {method: 'HEAD'}));
+        })
+      .then(function(response) {
+          assert_equals(response, undefined,
+                        'A HEAD request should not be matched');
+        });
+}, 'CacheStorageMatch a HEAD request');
+
 promise_test(function(test) {
     var transaction = create_unique_transaction();
     return self.caches.match(transaction.request)
@@ -117,7 +130,7 @@
         })
       .then(function(has_foo) {
           assert_false(has_foo, "The cache should still not exist.");
-        })
+        });
 }, 'CacheStorageMatch with no caches available but name provided');
 
 done();
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-async-waituntil.https.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-async-waituntil.https.html
index c06bf84..783a712e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-async-waituntil.https.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-async-waituntil.https.html
@@ -4,27 +4,79 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
 <script>
-promise_test(function(t) {
-    var script = 'resources/extendable-event-async-waituntil.js';
-    var scope = 'resources/async-waituntil';
-    var worker;
 
-    return service_worker_unregister_and_register(t, script, scope)
-      .then(function(registration) {
-          worker = registration.installing;
-          return wait_for_state(t, worker, 'activated');
-        })
-      .then(function() {
-          var channel = new MessageChannel();
-          var saw_message = new Promise(function(resolve) {
-              channel.port1.onmessage = function(e) { resolve(e.data); }
-            });
-          worker.postMessage({port: channel.port2}, [channel.port2]);
-          return saw_message;
-        })
-      .then(function(message) {
-          assert_equals(message, 'PASS');
-          return service_worker_unregister_and_done(t, scope);
-        })
-  }, 'Calling waitUntil asynchronously throws an exception');
+function sync_message(worker, message, transfer) {
+  let wait = new Promise((res, rej) => {
+    navigator.serviceWorker.addEventListener('message', function(e) {
+        if (e.data === 'ACK') {
+          res();
+        } else {
+          rej();
+        }
+      });
+    });
+  worker.postMessage(message, transfer);
+  return wait;
+}
+
+function runTest(test, step, testBody) {
+  var scope = './resources/' + step;
+  var script = 'resources/extendable-event-async-waituntil.js?' + scope;
+  service_worker_unregister_and_register(test, script, scope)
+    .then(function(registration) {
+        let worker = registration.installing;
+        var channel = new MessageChannel();
+        var saw_message = new Promise(function(resolve) {
+          channel.port1.onmessage = function(e) { resolve(e.data); }
+        });
+
+        wait_for_state(test, worker, 'activated')
+          .then(function() {
+              return sync_message(worker, { step: 'init', port: channel.port2 },
+                [channel.port2]);
+            })
+          .then(function() { return testBody(worker); })
+          .then(function() { return saw_message; })
+          .then(function(output) {
+              assert_equals(output.result, output.expected);
+            })
+          .then(function() { return sync_message(worker, { step: 'done' }); })
+          .then(() => { service_worker_unregister_and_done(test, scope); })
+          .catch(unreached_rejection(test));
+      });
+}
+
+function msg_event_test(scope, test) {
+  var testBody = function(worker) {
+    return sync_message(worker, { step: scope });
+  };
+  runTest(test, scope, testBody);
+}
+
+async_test(msg_event_test.bind(this, 'no-current-extension-different-task'),
+  'Test calling waitUntil in a different task without an existing extension throws');
+
+async_test(msg_event_test.bind(this, 'no-current-extension-different-microtask'),
+  'Test calling waitUntil in a different microtask without an existing extension throws');
+
+async_test(msg_event_test.bind(this, 'current-extension-different-task'),
+  'Test calling waitUntil in a different task with an existing extension succeeds');
+
+async_test(msg_event_test.bind(this, 'current-extension-expired-same-microtask-turn'),
+  'Test calling waitUntil with an existing extension promise handler succeeds');
+
+// The promise handler will queue a new microtask after the check for new
+// extensions was performed.
+async_test(msg_event_test.bind(this, 'current-extension-expired-same-microtask-turn-extra'),
+  'Test calling waitUntil at the end of the microtask turn throws');
+
+async_test(msg_event_test.bind(this, 'current-extension-expired-different-task'),
+  'Test calling waitUntil after the current extension expired in a different task fails');
+
+async_test(function(t) {
+    var testBody = function(worker) {
+      return with_iframe('./resources/pending-respondwith-async-waituntil/dummy.html');
+    }
+    runTest(t, 'pending-respondwith-async-waituntil', testBody);
+  }, 'Test calling waitUntil asynchronously with pending respondWith promise.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-waituntil.https.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-waituntil.https.html
index 003e703b..b2029ae 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-waituntil.https.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/extendable-event-waituntil.https.html
@@ -68,11 +68,13 @@
 async_test(function(t) {
     var scope = 'resources/install-reject-precedence';
     var onRegister = function(worker) {
+        var obj = {};
         wait_for_state(t, worker, 'redundant')
           .then(function() {
               service_worker_unregister_and_done(t, scope);
             })
           .catch(unreached_rejection(t));
+        syncWorker(t, worker, obj);
       };
     runTest(t, scope, onRegister);
   }, 'Test ExtendableEvent waitUntil reject precedence.');
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/fetch-event.https.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/fetch-event.https.html
index a9b0d6ca..6f8618b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/fetch-event.https.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/fetch-event.https.html
@@ -88,7 +88,7 @@
       .then(function(response_text) {
           assert_equals(
             response_text,
-            'Referrer: about:client\n' +
+            'Referrer: \n' +
             'ReferrerPolicy: no-referrer-when-downgrade',
             'Service Worker should respond to fetch with no referrer when a member of RequestInit is present with an HTTP request');
           return frame.contentWindow.fetch('resources/simple.html?referrerFull',
@@ -110,7 +110,7 @@
       .then(function(response_text) {
           assert_equals(
             response_text,
-            'Referrer: about:client\n' +
+            'Referrer: \n' +
             'ReferrerPolicy: no-referrer-when-downgrade',
             'Service Worker should respond to fetch with no referrer with ""');
           return frame.contentWindow.fetch('resources/simple.html?referrerFull',
@@ -176,7 +176,7 @@
       .then(function(response_text) {
           assert_equals(
             response_text,
-            'Referrer: about:client\n' +
+            'Referrer: \n' +
             'ReferrerPolicy: no-referrer-when-downgrade',
             'Service Worker should respond to fetch with no referrer with "no-referrer-when-downgrade" and an HTTP request');
           var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
@@ -197,9 +197,99 @@
       .then(function(response_text) {
           assert_equals(
             response_text,
-            'Referrer: about:client\n' +
+            'Referrer: \n' +
             'ReferrerPolicy: no-referrer',
             'Service Worker should respond to fetch with no referrer URL with "no-referrer"');
+          return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+                                           {referrerPolicy: "same-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: ' + href + '\n' +
+            'ReferrerPolicy: same-origin',
+            'Service Worker should respond to fetch with referrer URL with "same-origin" and a same origin request');
+          var http_url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() +
+                         '/resources/simple.html?referrerFull';
+          return frame.contentWindow.fetch(http_url,
+                                           {referrerPolicy: "same-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: \n' +
+            'ReferrerPolicy: same-origin',
+            'Service Worker should respond to fetch with no referrer with "same-origin" and cross origin request');
+          var http_url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() +
+                         '/resources/simple.html?referrerFull';
+          return frame.contentWindow.fetch(http_url,
+                                           {referrerPolicy: "strict-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: ' + origin + '/' + '\n' +
+            'ReferrerPolicy: strict-origin',
+            'Service Worker should respond to fetch with the referrer origin  with "strict-origin" and a HTTPS cross origin request');
+          return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+                                           {referrerPolicy: "strict-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: ' + origin + '/' + '\n' +
+            'ReferrerPolicy: strict-origin',
+            'Service Worker should respond to fetch with the referrer origin with "strict-origin" and a same origin request');
+          var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+                         '/resources/simple.html?referrerFull';
+          return frame.contentWindow.fetch(http_url,
+                                           {referrerPolicy: "strict-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: \n' +
+            'ReferrerPolicy: strict-origin',
+            'Service Worker should respond to fetch with no referrer with "strict-origin" and a HTTP request');
+          return frame.contentWindow.fetch('resources/simple.html?referrerFull',
+                                           {referrerPolicy: "strict-origin-when-cross-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: ' + href + '\n' +
+            'ReferrerPolicy: strict-origin-when-cross-origin',
+            'Service Worker should respond to fetch with the referrer URL with "strict-origin-when-cross-origin" and a same origin request');
+          var http_url = get_host_info()['HTTPS_REMOTE_ORIGIN'] + base_path() +
+                         '/resources/simple.html?referrerFull';
+          return frame.contentWindow.fetch(http_url,
+                                           {referrerPolicy: "strict-origin-when-cross-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: ' + origin + '/' + '\n' +
+            'ReferrerPolicy: strict-origin-when-cross-origin',
+            'Service Worker should respond to fetch with the referrer origin with "strict-origin-when-cross-origin" and a HTTPS cross origin request');
+          var http_url = get_host_info()['HTTP_ORIGIN'] + base_path() +
+                         '/resources/simple.html?referrerFull';
+          return frame.contentWindow.fetch(http_url,
+                                           {referrerPolicy: "strict-origin-when-cross-origin", referrer: referrer});
+        })
+      .then(function(response) { return response.text(); })
+      .then(function(response_text) {
+          assert_equals(
+            response_text,
+            'Referrer: \n' +
+            'ReferrerPolicy: strict-origin-when-cross-origin',
+            'Service Worker should respond to fetch with no referrer with "strict-origin-when-cross-origin" and a HTTP request');
         });
 }
 
@@ -419,14 +509,13 @@
       .then(function(frame) {
           assert_equals(
             frame.contentDocument.body.textContent,
-            'Fragment Not Found',
-            'Service worker should not expose URL fragments.');
+            'Fragment Found :' + fragment,
+            'Service worker should expose URL fragments in request.');
           frame.remove();
           return service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Service Worker must not expose FetchEvent URL fragments.');
-
 async_test(function(t) {
     var scope = 'resources/simple.html?cache';
     var frame;
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/oninstall-script-error.https.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/oninstall-script-error.https.html
index a9ca19ca..fe7f6e9 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/oninstall-script-error.https.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/oninstall-script-error.https.html
@@ -33,18 +33,23 @@
   {
     name: 'install handler throws an error',
     script: 'resources/oninstall-throw-error-worker.js',
-    expect_install: false
+    expect_install: true
   },
   {
     name: 'install handler throws an error, error handler does not cancel',
     script: 'resources/oninstall-throw-error-with-empty-onerror-worker.js',
-    expect_install: false
+    expect_install: true
   },
   {
     name: 'install handler dispatches an event that throws an error',
     script: 'resources/oninstall-throw-error-from-nested-event-worker.js',
     expect_install: true
   },
+  {
+    name: 'install handler throws an error in the waitUntil',
+    script: 'resources/oninstall-waituntil-throw-error-worker.js',
+    expect_install: false
+  },
 
   // The following two cases test what happens when the ServiceWorkerGlobalScope
   // 'error' event handler cancels the resulting error event.  Since the
@@ -54,12 +59,12 @@
   {
     name: 'install handler throws an error that is cancelled',
     script: 'resources/oninstall-throw-error-then-cancel-worker.js',
-    expect_install: false
+    expect_install: true
   },
   {
     name: 'install handler throws an error and prevents default',
     script: 'resources/oninstall-throw-error-then-prevent-default-worker.js',
-    expect_install: false
+    expect_install: true
   }
 ].forEach(function(test_case) {
     make_test(test_case.name, test_case.script, test_case.expect_install);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/performance-timeline.https.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/performance-timeline.https.html
index 182076b..d2ed677 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/performance-timeline.https.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/performance-timeline.https.html
@@ -8,4 +8,45 @@
     'resources/performance-timeline-worker.js',
     'Test Performance Timeline API in Service Worker');
 
+// The purpose of this test is to verify that service worker overhead
+// is included in the Performance API's timing information.
+promise_test(t => {
+  let script = 'resources/empty-but-slow-worker.js';
+  let scope = 'resources/dummy.txt?slow-sw-timing';
+  let url = new URL(scope, window.location).href;
+  let slowURL = url + '&slow';
+  let frame;
+  return service_worker_unregister_and_register(t, script, scope)
+    .then(reg => wait_for_state(t, reg.installing, 'activated'))
+    .then(_ => with_iframe(scope))
+    .then(f => {
+      frame = f;
+      return Promise.all([
+        // This will get effectively an empty service worker FetchEvent
+        // handler.  It should have no additional delay.  Note that the
+        // text() call is necessary to complete the response and have the
+        // timings show up in the performance entries.
+        frame.contentWindow.fetch(url).then(r => r && r.text()),
+        // This will cause the service worker to spin for two seconds
+        // in its FetchEvent handler.
+        frame.contentWindow.fetch(slowURL).then(r => r && r.text())
+      ]);
+    })
+    .then(_ => {
+      function elapsed(u) {
+        let entry = frame.contentWindow.performance.getEntriesByName(u);
+        return entry[0] ? entry[0].duration : undefined;
+      }
+      let urlTime = elapsed(url);
+      let slowURLTime = elapsed(slowURL);
+      // Verify the request slowed by the service worker is indeed measured
+      // to be slower.  Note, we compare to smaller delay instead of the exact
+      // delay amount to avoid making the test racy under automation.
+      assert_true(slowURLTime >= urlTime + 1500,
+                  'Slow service worker request should measure increased delay.');
+      frame.remove();
+      return service_worker_unregister_and_done(t, scope);
+    })
+}, 'empty service worker fetch event included in performance timings');
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/empty-but-slow-worker.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/empty-but-slow-worker.js
new file mode 100644
index 0000000..92abac7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/empty-but-slow-worker.js
@@ -0,0 +1,8 @@
+addEventListener('fetch', evt => {
+  if (evt.request.url.endsWith('slow')) {
+    // Performance.now() might be a bit better here, but Date.now() has
+    // better compat in workers right now.
+    let start = Date.now();
+    while(Date.now() - start < 2000);
+  }
+});
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/extendable-event-async-waituntil.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/extendable-event-async-waituntil.js
index d77238d9..21b4e28 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/extendable-event-async-waituntil.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/extendable-event-async-waituntil.js
@@ -1,20 +1,100 @@
-var result = 'FAIL: did not throw.';
+// controlled by 'init'/'done' messages.
+var resolveLockPromise;
+var port;
 
 self.addEventListener('message', function(event) {
-    event.data.port.postMessage(result);
-  });
+    var waitPromise;
+    var resolveTestPromise;
 
-self.addEventListener('install', function(event) {
-    self.installEvent = event;
-  });
-
-self.addEventListener('activate', function(event) {
-    try {
-      self.installEvent.waitUntil(new Promise(function(){}));
-    } catch (error) {
-      if (error.name == 'InvalidStateError')
-        result = 'PASS';
-      else
-        result = 'FAIL: unexpected exception: ' + error;
+    switch (event.data.step) {
+      case 'init':
+        event.waitUntil(new Promise((res) => { resolveLockPromise = res; }));
+        port = event.data.port;
+        break;
+      case 'done':
+        resolveLockPromise();
+        break;
+      case 'no-current-extension-different-task':
+        async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
+        break;
+      case 'no-current-extension-different-microtask':
+        async_microtask_waituntil(event).then(reportResultExpecting('InvalidStateError'));
+        break;
+      case 'current-extension-different-task':
+        event.waitUntil(new Promise((res) => { resolveTestPromise = res; }));
+        async_task_waituntil(event).then(reportResultExpecting('OK')).then(resolveTestPromise);
+        break;
+      case 'current-extension-expired-same-microtask-turn':
+        waitPromise = Promise.resolve();
+        event.waitUntil(waitPromise);
+        waitPromise.then(() => { return sync_waituntil(event); })
+          .then(reportResultExpecting('OK'))
+        break;
+      case 'current-extension-expired-same-microtask-turn-extra':
+        // The promise handler queues a new microtask *after* the check for new
+        // extensions was performed.
+        waitPromise = Promise.resolve();
+        event.waitUntil(waitPromise);
+        waitPromise.then(() => { return async_microtask_waituntil(event); })
+          .then(reportResultExpecting('InvalidStateError'))
+        break;
+      case 'current-extension-expired-different-task':
+        event.waitUntil(Promise.resolve());
+        async_task_waituntil(event).then(reportResultExpecting('InvalidStateError'));
+        break;
     }
+    event.source.postMessage('ACK');
   });
+
+self.addEventListener('fetch', function(event) {
+    var resolveFetch;
+    let response = new Promise((res) => { resolveFetch = res; });
+    event.respondWith(response);
+    async_task_waituntil(event)
+      .then(reportResultExpecting('OK'))
+      .then(() => { resolveFetch(new Response('OK')); });
+  });
+
+function reportResultExpecting(expectedResult) {
+  return function (result) {
+    port.postMessage({result : result, expected: expectedResult});
+    return result;
+  };
+}
+
+function sync_waituntil(event) {
+  return new Promise((res, rej) => {
+    try {
+        event.waitUntil(Promise.resolve());
+        res('OK');
+      } catch (error) {
+        res(error.name);
+      }
+  });
+}
+
+function async_microtask_waituntil(event) {
+  return new Promise((res, rej) => {
+    Promise.resolve().then(() => {
+      try {
+        event.waitUntil(Promise.resolve());
+        res('OK');
+      } catch (error) {
+        res(error.name);
+      }
+    });
+  });
+}
+
+function async_task_waituntil(event) {
+  return new Promise((res, rej) => {
+    setTimeout(() => {
+      try {
+        event.waitUntil(Promise.resolve());
+        res('OK');
+      } catch (error) {
+        res(error.name);
+      }
+    }, 0);
+  });
+}
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html
index 48f61839..121c5c34 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-cors-xhr-iframe.html
@@ -66,8 +66,8 @@
       [remote_url + '?reject', false, FAIL],
       [remote_url + '?reject', true, FAIL],
       // Event handler exception tests
-      [url + '?throw', false, FAIL],
-      [url + '?throw', true, FAIL],
+      [url + '?throw', false, SUCCESS],
+      [url + '?throw', true, SUCCESS],
       [remote_url + '?throw', false, FAIL],
       [remote_url + '?throw', true, FAIL],
       // Reject(resolve-null) tests
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html
index a4c9307e..e28f623 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-controllee-iframe.html
@@ -40,7 +40,8 @@
     { name: 'unused-body', expect_load: true },
     { name: 'used-body', expect_load: false },
     { name: 'unused-fetched-body', expect_load: true },
-    { name: 'used-fetched-body', expect_load: false }
+    { name: 'used-fetched-body', expect_load: false },
+    { name: 'throw-exception', expect_load: true },
   ].map(make_test);
 
   Promise.all(tests)
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-worker.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-worker.js
index 52d4c8e..5bfe3a0b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-worker.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-network-error-worker.js
@@ -32,6 +32,9 @@
           return res;
         }));
       break;
+    case '?throw-exception':
+      throw('boom');
+      break;
     }
   });
 
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-test-worker.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-test-worker.js
index 32a1b4f2..55ba4ab 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-test-worker.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-event-test-worker.js
@@ -80,21 +80,19 @@
         'bodyUsed: ' + lastResponseForUsedCheck.bodyUsed));
   }
 }
-
 function handleFragmentCheck(event) {
   var body;
   if (event.request.url.indexOf('#') === -1) {
     body = 'Fragment Not Found';
   } else {
-    body = 'Fragment Found';
+    body = 'Fragment Found :' +
+           event.request.url.substring(event.request.url.indexOf('#'));
   }
   event.respondWith(new Response(body));
 }
-
 function handleCache(event) {
   event.respondWith(new Response(event.request.cache));
 }
-
 function handleEventSource(event) {
   if (event.request.mode === 'navigate') {
     return;
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-rewrite-worker.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-rewrite-worker.js
index 4a7646e..9806f2b 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-rewrite-worker.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/fetch-rewrite-worker.js
@@ -76,6 +76,7 @@
       } else {
         event.respondWith(new Response('NO_ACCEPT'));
       }
+      return;
     }
     event.respondWith(new Promise(function(resolve, reject) {
         var request = event.request;
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js
new file mode 100644
index 0000000..6cb8f6e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/service-workers/service-worker/resources/oninstall-waituntil-throw-error-worker.js
@@ -0,0 +1,5 @@
+self.addEventListener('install', function(event) {
+  event.waitUntil(new Promise(function(aRequest, aResponse) {
+      throw new Error();
+    }));
+});
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/uievents/order-of-events/focus-events/focus-automated-blink-webkit.html b/third_party/WebKit/LayoutTests/imported/wpt/uievents/order-of-events/focus-events/focus-automated-blink-webkit.html
new file mode 100644
index 0000000..41de454
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/uievents/order-of-events/focus-events/focus-automated-blink-webkit.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<!-- Modified from Chris Rebert's manual version -->
+<!-- This documents the behavior according to blink's implementation -->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Focus-related events should fire in the correct order</title>
+    <link rel="help" href="https://w3c.github.io/uievents/#events-focusevent-event-order">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body id="body">
+    <input type="text" id="a" value="First">
+    <input type="text" id="b" value="Second">
+    <br>
+    <input type="text" id="c" value="Third">
+    <iframe id="iframe">
+    </iframe>
+    <br>
+    <script>
+
+    var test_id = 0;
+    var tests = ['normal', 'iframe']
+
+    function record(evt) {
+      if (done && (evt.type == 'focusin' || evt.type == 'focus') && (evt.target == c)) {
+          startNext();
+      }
+      if (!done) {
+          var activeElement = document.activeElement ?
+            (document.activeElement.tagName === 'IFRAME' ?
+            document.activeElement.contentDocument.activeElement.id :
+            document.activeElement.id) : null;
+          events[tests[test_id]].push(evt.type);
+          targets[tests[test_id]].push(evt.target.id);
+          focusedElements[tests[test_id]].push(activeElement);
+          relatedTargets[tests[test_id]].push(evt.relatedTarget ? evt.relatedTarget.id : null);
+      }
+    }
+    function startNext() {
+        done = false;
+        test_id++;
+    }
+    function finish() {
+        done = true;
+    }
+    var relevantEvents = [
+      'focus',
+      'blur',
+      'focusin',
+      'focusout'
+    ];
+
+    var iframe = document.getElementById('iframe');
+    var a = document.getElementById('a');
+    var b = document.getElementById('b');
+    var c = document.getElementById('c');
+    var d = document.createElement('input');
+
+    d.setAttribute('id', 'd');
+    d.setAttribute('type', 'text');
+    d.setAttribute('value', 'Fourth');
+
+    var events = {'normal': [], 'iframe': []};
+    var targets = {'normal': [], 'iframe': []};
+    var focusedElements = {'normal': [], 'iframe': []};
+    var relatedTargets = {'normal': [], 'iframe': []};
+    var done = false;
+
+    var async_test_normal = async_test('Focus-related events should fire in the correct order (same DocumentOwner)');
+    var async_test_iframe_static = async_test('Focus-related events should fire in the correct order (different DocumentOwner)');
+
+    window.onload = function(evt) {
+
+        iframe.contentDocument.body.appendChild(d);
+
+        var inputs = [a, b, c, d];
+
+        for (var i = 0; i < inputs.length; i++) {
+          for (var k = 0; k < relevantEvents.length; k++) {
+            inputs[i].addEventListener(relevantEvents[k], record, false);
+          }
+        }
+
+        a.addEventListener('focusin', function() { b.focus(); }, false);
+        b.addEventListener('focusin', function() {
+            async_test_normal.step( function() {
+                assert_array_equals(
+                  events['normal'],
+                  ['focus', 'focusin', 'blur', 'focusout', 'focus', 'focusin'],
+                  'Focus-related events should fire in this order: focusin, focus, focusout, focusin, blur, focus'
+                );
+
+                assert_array_equals(
+                  targets['normal'],
+                  [      'a',     'a',        'a',       'a',    'b',     'b'],
+                  'Focus-related events should fire at the correct targets'
+                );
+
+                assert_array_equals(
+                  relatedTargets['normal'],
+                  [    null,    null,         'b',       'b',    'a',     'a'],
+                  'Focus-related events should reference correct relatedTargets'
+                );
+
+                assert_array_equals(
+                  focusedElements['normal'],
+                  [   'a',     'a',        'body',    'body',    'b',     'b'],
+                  'Focus-related events should fire at the correct time relative to actual focus changes'
+                );
+
+                async_test_normal.done();
+                });
+
+            b.addEventListener('focusout', function() { finish(); c.focus(); });
+            b.blur();
+
+            }, false);
+
+        c.addEventListener('focusin', function() {d.focus();});
+        d.addEventListener('focusin', function() {
+            async_test_iframe_static.step(function() {
+                assert_array_equals(
+                  events['iframe'],
+                  ['focus', 'focusin', 'blur', 'focusout', 'focus', 'focusin'],
+                  'Focus-related events should fire in this order: focusin, focus, focusout, focusin, blur, focus'
+                );
+
+                assert_array_equals(
+                  targets['iframe'],
+                  [      'c',     'c',        'c',       'c',    'd',     'd'],
+                  'Focus-related events should fire at the correct targets'
+                );
+
+                assert_array_equals(
+                  relatedTargets['iframe'],
+                  [    null,    null,        null,      null,   null,    null],
+                  'Focus-related events should reference correct relatedTargets'
+                );
+
+                assert_array_equals(
+                  focusedElements['iframe'],
+                  [   'c',     'c',        'body',      'body', 'd',     'd'],
+                  'Focus-related events should fire at the correct time relative to actual focus changes'
+                );
+
+                async_test_iframe_static.done();
+                });
+
+            d.addEventListener('focusout', function() { finish();});
+
+          }, false);
+
+        a.focus();
+    }
+
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/user-timing/resources/webperftestharness.js b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/resources/webperftestharness.js
index 750946d..f1597bbe 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/user-timing/resources/webperftestharness.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/resources/webperftestharness.js
@@ -12,7 +12,7 @@
 // Helper Functions for NavigationTiming W3C tests
 //
 
-var performanceNamespace = window.performance;
+var performanceNamespace = self.performance;
 var timingAttributes = [
     'connectEnd',
     'connectStart',
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html
new file mode 100644
index 0000000..aea8cb6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>exception test of performance.mark and performance.measure</title>
+    <meta rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/webperftestharness.js"></script>
+  </head>
+  <body>
+    <script>
+    setup({explicit_done: true});
+    test_namespace();
+
+    test(function() {
+      for (var i in timingAttributes) {
+        assert_throws("SyntaxError", function() { window.performance.mark(timingAttributes[i]); });
+        assert_throws("SyntaxError", function() { window.performance.measure(timingAttributes[i]); });
+      }
+    }, "performance.mark and performance.measure should throw if used with timing attribute values");
+
+    fetch_tests_from_worker(new Worker("test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js"));
+
+    done();
+
+    </script>
+    <h1>Description</h1>
+    <p>This test validates exception scenarios of invoking mark() and measure() with timing attributes as value.</p>
+    <div id="log"></div>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js
new file mode 100644
index 0000000..f015402
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_with_timing_attributes.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+importScripts("resources/webperftestharness.js");
+
+test(function() {
+  for (var i in timingAttributes) {
+    performance.mark(timingAttributes[i]);
+    performance.clearMarks(timingAttributes[i]);
+
+    performance.measure(timingAttributes[i]);
+    performance.clearMeasures(timingAttributes[i]);
+  }
+}, "performance.mark and performance.measure should not throw if used with timing attribute values in workers");
+
+done();
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/addition-per-property.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/addition-per-property.html
new file mode 100644
index 0000000..b6210c6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/addition-per-property.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Tests for animation type of addition</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="property-list.js"></script>
+<script src="property-types.js"></script>
+<style>
+html {
+  font-size: 10px;
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (var property in gCSSProperties) {
+  if (!isSupported(property)) {
+    continue;
+  }
+
+  var animationTypes = gCSSProperties[property].types;
+  var setupFunction = gCSSProperties[property].setup;
+  animationTypes.forEach(function(animationType) {
+    var typeObject;
+    var animationTypeString;
+    if (typeof animationType === 'string') {
+      typeObject = types[animationType];
+      animationTypeString = animationType;
+    } else if (typeof animationType === 'object' &&
+               animationType.type && typeof animationType.type === 'string') {
+      typeObject = types[animationType.type];
+      animationTypeString = animationType.type;
+    }
+
+    // First, test that the animation type object has 'testAddition'.
+    // We use test() function here so that we can continue the remainder tests
+    // even if this test fails.
+    test(function(t) {
+      assert_own_property(typeObject, 'testAddition', animationTypeString +
+                          ' should have testAddition property');
+      assert_equals(typeof typeObject.testAddition, 'function',
+                    'testAddition method should be a function');
+    }, animationTypeString + ' has testAddition function');
+
+    if (typeObject.testAddition &&
+        typeof typeObject.testAddition === 'function') {
+      typeObject.testAddition(property,
+                              setupFunction,
+                              animationType.options);
+    }
+  });
+}
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/interpolation-per-property.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/interpolation-per-property.html
new file mode 100644
index 0000000..9062116
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/interpolation-per-property.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests for animation type of interpolation</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<script src="property-list.js"></script>
+<script src="property-types.js"></script>
+<style>
+html {
+  font-size: 10px;
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+for (var property in gCSSProperties) {
+  if (!isSupported(property)) {
+    continue;
+  }
+
+  var animationTypes = gCSSProperties[property].types;
+  var setupFunction = gCSSProperties[property].setup;
+  animationTypes.forEach(function(animationType) {
+    var typeObject;
+    var animationTypeString;
+    if (typeof animationType === 'string') {
+      typeObject = types[animationType];
+      animationTypeString = animationType;
+    } else if (typeof animationType === 'object' &&
+               animationType.type && typeof animationType.type === 'string') {
+      typeObject = types[animationType.type];
+      animationTypeString = animationType.type;
+    }
+
+    // First, test that the animation type object has 'testInterpolation'.
+    // We use test() function() here so that we can continue the remainder tests
+    // even if this test fails.
+    test(function(t) {
+      assert_own_property(typeObject, 'testInterpolation', animationTypeString +
+                          ' should have testInterpolation property');
+      assert_equals(typeof typeObject.testInterpolation, 'function',
+                    'testInterpolation method should be a function');
+    }, animationTypeString + ' has testInterpolation function');
+
+    if (typeObject.testInterpolation &&
+        typeof typeObject.testInterpolation === 'function') {
+      typeObject.testInterpolation(property,
+                                   setupFunction,
+                                   animationType.options);
+    }
+  });
+}
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-list.js b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-list.js
new file mode 100644
index 0000000..a2f47285
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-list.js
@@ -0,0 +1,1523 @@
+'use strict';
+
+var gCSSProperties = {
+  'align-content': {
+    // https://drafts.csswg.org/css-align/#propdef-align-content
+    types: [
+      { type: 'discrete' , options: [ [ 'flex-start', 'flex-end' ] ] }
+    ]
+  },
+  'align-items': {
+    // https://drafts.csswg.org/css-align/#propdef-align-items
+    types: [
+      { type: 'discrete', options: [ [ 'flex-start', 'flex-end' ] ] }
+    ]
+  },
+  'align-self': {
+    // https://drafts.csswg.org/css-align/#propdef-align-self
+    types: [
+      { type: 'discrete', options: [ [ 'flex-start', 'flex-end' ] ] }
+    ]
+  },
+  'backface-visibility': {
+    // https://drafts.csswg.org/css-transforms/#propdef-backface-visibility
+    types: [
+      { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] }
+    ]
+  },
+  'background-attachment': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-attachment
+    types: [
+      { type: 'discrete', options: [ [ 'fixed', 'local' ] ] }
+    ]
+  },
+  'background-color': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-color
+    types: [ 'color' ]
+  },
+  'background-blend-mode': {
+    // https://drafts.fxtf.org/compositing-1/#propdef-background-blend-mode
+    types: [
+      { type: 'discrete', options: [ [ 'multiply', 'screen' ] ] }
+    ]
+  },
+  'background-clip': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-clip
+    types: [
+      { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] }
+    ]
+  },
+  'background-image': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-image
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                   'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'background-origin': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-origin
+    types: [
+      { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] }
+    ]
+  },
+  'background-position': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-position
+    types: [
+    ]
+  },
+  'background-position-x': {
+    // https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
+    types: [
+    ]
+  },
+  'background-position-y': {
+    // https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y
+    types: [
+    ]
+  },
+  'background-repeat': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-repeat
+    types: [
+      { type: 'discrete', options: [ [ 'space', 'round' ] ] }
+    ]
+  },
+  'background-size': {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-size
+    types: [
+    ]
+  },
+  'block-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-block-size
+    types: [
+    ]
+  },
+  'border-block-end-color': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-color
+    types: [
+    ]
+  },
+  'border-block-end-style': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-style
+    types: [
+    ]
+  },
+  'border-block-end-width': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-width
+    types: [
+    ]
+  },
+  'border-block-start-color': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-color
+    types: [
+    ]
+  },
+  'border-block-start-style': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-style
+    types: [
+    ]
+  },
+  'border-block-start-width': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-width
+    types: [
+    ]
+  },
+  'border-bottom-color': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-color
+    types: [ 'color' ]
+  },
+  'border-bottom-left-radius': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-left-radius
+    types: [
+    ]
+  },
+  'border-bottom-right-radius': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-right-radius
+    types: [
+    ]
+  },
+  'border-bottom-style': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-style
+    types: [
+      { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] }
+    ]
+  },
+  'border-bottom-width': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.borderBottomStyle = 'solid';
+      return element;
+    }
+  },
+  'border-collapse': {
+    // https://drafts.csswg.org/css-tables/#propdef-border-collapse
+    types: [
+      { type: 'discrete', options: [ [ 'collapse', 'separate' ] ] }
+    ]
+  },
+  'border-inline-end-color': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-color
+    types: [
+    ]
+  },
+  'border-inline-end-style': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-style
+    types: [
+    ]
+  },
+  'border-inline-end-width': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-width
+    types: [
+    ]
+  },
+  'border-inline-start-color': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-start-color
+    types: [
+    ]
+  },
+  'border-inline-start-style': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-style
+    types: [
+    ]
+  },
+  'border-inline-start-width': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-start-width
+    types: [
+    ]
+  },
+  'border-image-outset': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-outset
+    types: [
+      { type: 'discrete', options: [ [ '1 1 1 1', '5 5 5 5' ] ] }
+    ]
+  },
+  'border-image-repeat': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-repeat
+    types: [
+      { type: 'discrete', options: [ [ 'stretch stretch', 'repeat repeat' ] ] }
+    ]
+  },
+  'border-image-slice': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-slice
+    types: [
+      { type: 'discrete', options: [ [ '1 1 1 1', '5 5 5 5' ] ] }
+    ]
+  },
+  'border-image-source': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-source
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                   'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'border-image-width': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-width
+    types: [
+      { type: 'discrete', options: [ [ '1 1 1 1', '5 5 5 5' ] ] }
+    ]
+  },
+  'border-left-color': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-left-color
+    types: [ 'color' ]
+  },
+  'border-left-style': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-left-style
+    types: [
+      { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] }
+    ]
+  },
+  'border-left-width': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-left-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.borderLeftStyle = 'solid';
+      return element;
+    }
+  },
+  'border-right-color': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-right-color
+    types: [ 'color' ]
+  },
+  'border-right-style': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-right-style
+    types: [
+      { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] }
+    ]
+  },
+  'border-right-width': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-right-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.borderRightStyle = 'solid';
+      return element;
+    }
+  },
+  'border-spacing': {
+    // https://drafts.csswg.org/css-tables/#propdef-border-spacing
+    types: [
+    ]
+  },
+  'border-top-color': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-color
+    types: [ 'color' ]
+  },
+  'border-top-left-radius': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-left-radius
+    types: [
+    ]
+  },
+  'border-top-right-radius': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-right-radius
+    types: [
+    ]
+  },
+  'border-top-style': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-style
+    types: [
+      { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] }
+    ]
+  },
+  'border-top-width': {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.borderTopStyle = 'solid';
+      return element;
+    }
+  },
+  'bottom': {
+    // https://drafts.csswg.org/css-position/#propdef-bottom
+    types: [
+    ]
+  },
+  'box-decoration-break': {
+    // https://drafts.csswg.org/css-break/#propdef-box-decoration-break
+    types: [
+      { type: 'discrete', options: [ [ 'slice', 'clone' ] ] }
+    ]
+  },
+  'box-shadow': {
+    // https://drafts.csswg.org/css-backgrounds/#box-shadow
+    types: [ 'boxShadowList' ],
+  },
+  'box-sizing': {
+    // https://drafts.csswg.org/css-ui-4/#box-sizing
+    types: [
+      { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] }
+    ]
+  },
+  'caption-side': {
+    // https://drafts.csswg.org/css-tables/#propdef-caption-side
+    types: [
+      { type: 'discrete', options: [ [ 'top', 'bottom' ] ] }
+    ]
+  },
+  'clear': {
+    // https://drafts.csswg.org/css-page-floats/#propdef-clear
+    types: [
+      { type: 'discrete', options: [ [ 'inline-start', 'inline-end' ] ] }
+    ]
+  },
+  'clip': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-clip
+    types: [
+    ]
+  },
+  'clip-path': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-clip-path
+    types: [
+    ]
+  },
+  'clip-rule': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule
+    types: [
+      { type: 'discrete', options: [ [ 'evenodd', 'nonzero' ] ] }
+    ]
+  },
+  'color': {
+    // https://drafts.csswg.org/css-color/#propdef-color
+    types: [ 'color' ]
+  },
+  'color-adjust': {
+    // https://drafts.csswg.org/css-color-4/#color-adjust
+    types: [
+      { type: 'discrete', options: [ [ 'economy', 'exact' ] ] }
+    ]
+  },
+  'color-interpolation': {
+    // https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty
+    types: [
+      { type: 'discrete', options: [ [ 'linearRGB', 'auto' ] ] }
+    ]
+  },
+  'color-interpolation-filters': {
+    // https://drafts.fxtf.org/filters-1/#propdef-color-interpolation-filters
+    types: [
+      { type: 'discrete', options: [ [ 'sRGB', 'linearRGB' ] ] }
+    ]
+  },
+  'column-count': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-count
+    types: [
+    ]
+  },
+  'column-gap': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-gap
+    types: [ 'length' ]
+  },
+  'column-rule-color': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-rule-color
+    types: [ 'color' ]
+  },
+  'column-fill': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-fill
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'balance' ] ] }
+    ]
+  },
+  'column-rule-style': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-rule-style
+    types: [
+      { type: 'discrete', options: [ [ 'none', 'dotted' ] ] }
+    ]
+  },
+  'column-rule-width': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-rule-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.columnRuleStyle = 'solid';
+      return element;
+    }
+  },
+  'column-width': {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-width
+    types: [ 'length',
+      { type: 'discrete', options: [ [ 'auto', '1px' ] ] }
+    ]
+  },
+  'contain': {
+    // https://drafts.csswg.org/css-containment/#propdef-contain
+    types: [
+      { type: 'discrete', options: [ [ 'strict', 'none' ] ] }
+    ]
+  },
+  'content': {
+    // https://drafts.csswg.org/css-content-3/#propdef-content
+    types: [
+      { type: 'discrete', options: [ [ '"a"', '"b"' ] ] }
+    ],
+    setup: t => {
+      return createPseudo(t, 'before');
+    }
+  },
+  'counter-increment': {
+    // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
+    types: [
+      { type: 'discrete', options: [ [ 'ident-1 1', 'ident-2 2' ] ] }
+    ]
+  },
+  'counter-reset': {
+    // https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
+    types: [
+      { type: 'discrete', options: [ [ 'ident-1 1', 'ident-2 2' ] ] }
+    ]
+  },
+  'cursor': {
+    // https://drafts.csswg.org/css2/ui.html#propdef-cursor
+    types: [
+      { type: 'discrete', options: [ [ 'pointer', 'wait' ] ] }
+    ]
+  },
+  'direction': {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-direction
+    types: [
+      { type: 'discrete', options: [ [ 'ltr', 'rtl' ] ] }
+    ]
+  },
+  'dominant-baseline': {
+    // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline
+    types: [
+      { type: 'discrete', options: [ [ 'ideographic', 'alphabetic' ] ] }
+    ]
+  },
+  'empty-cells': {
+    // https://drafts.csswg.org/css-tables/#propdef-empty-cells
+    types: [
+      { type: 'discrete', options: [ [ 'show', 'hide' ] ] }
+    ]
+  },
+  'fill': {
+    // https://svgwg.org/svg2-draft/painting.html#FillProperty
+    types: [
+    ]
+  },
+  'fill-opacity': {
+    // https://svgwg.org/svg2-draft/painting.html#FillOpacityProperty
+    types: [
+    ]
+  },
+  'fill-rule': {
+    // https://svgwg.org/svg2-draft/painting.html#FillRuleProperty
+    types: [
+      { type: 'discrete', options: [ [ 'evenodd', 'nonzero' ] ] }
+    ]
+  },
+  'filter': {
+    // https://drafts.fxtf.org/filters/#propdef-filter
+    types: [ 'filterList' ]
+  },
+  'flex-basis': {
+    // https://drafts.csswg.org/css-flexbox/#propdef-flex-basis
+    types: [
+      'lengthPercentageOrCalc',
+      { type: 'discrete', options: [ [ 'auto', '10px' ] ] }
+    ]
+  },
+  'flex-direction': {
+    // https://drafts.csswg.org/css-flexbox/#propdef-flex-direction
+    types: [
+      { type: 'discrete', options: [ [ 'row', 'row-reverse' ] ] }
+    ]
+  },
+  'flex-grow': {
+    // https://drafts.csswg.org/css-flexbox/#flex-grow-property
+    types: [ 'positiveNumber' ]
+  },
+  'flex-shrink': {
+    // https://drafts.csswg.org/css-flexbox/#propdef-flex-shrink
+    types: [ 'positiveNumber' ]
+  },
+  'flex-wrap': {
+    // https://drafts.csswg.org/css-flexbox/#propdef-flex-wrap
+    types: [
+      { type: 'discrete', options: [ [ 'nowrap', 'wrap' ] ] }
+    ]
+  },
+  'flood-color': {
+    // https://drafts.fxtf.org/filters/#FloodColorProperty
+    types: [ 'color' ]
+  },
+  'flood-opacity': {
+    // https://drafts.fxtf.org/filters/#propdef-flood-opacity
+    types: [
+    ]
+  },
+  'font-size': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-size
+    types: [
+    ]
+  },
+  'font-size-adjust': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-size-adjust
+    types: [
+    ]
+  },
+  'font-stretch': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-stretch
+    types: [
+    ]
+  },
+  'font-style': {
+    // https://drafts.csswg.org/css-fonts/#propdef-font-style
+    types: [
+      { type: 'discrete', options: [ [ 'italic', 'oblique' ] ] }
+    ]
+  },
+  'float': {
+    // https://drafts.csswg.org/css-page-floats/#propdef-float
+    types: [
+      { type: 'discrete', options: [ [ 'left', 'right' ] ] }
+    ]
+  },
+  'font-family': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-family
+    types: [
+      { type: 'discrete', options: [ [ 'helvetica', 'verdana' ] ] }
+    ]
+  },
+  'font-feature-settings': {
+    // https://drafts.csswg.org/css-fonts/#descdef-font-feature-settings
+    types: [
+      { type: 'discrete', options: [ [ '"liga" 5', 'normal' ] ] }
+    ]
+  },
+  'font-kerning': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-kerning
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'normal' ] ] }
+    ]
+  },
+  'font-language-override': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override
+    types: [
+      { type: 'discrete', options: [ [ '"eng"', 'normal' ] ] }
+    ]
+  },
+  'font-style': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-style
+    types: [
+      { type: 'discrete', options: [ [ 'italic', 'oblique' ] ] }
+    ]
+  },
+  'font-synthesis': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-synthesis
+    types: [
+      { type: 'discrete', options: [ [ 'none', 'weight style' ] ] }
+    ]
+  },
+  'font-variant-alternates': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-alternates
+    types: [
+      { type: 'discrete',
+        options: [ [ 'swash(unknown)', 'stylistic(unknown)' ] ] }
+    ]
+  },
+  'font-variant-caps': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-caps
+    types: [
+      { type: 'discrete', options: [ [ 'small-caps', 'unicase' ] ] }
+    ]
+  },
+  'font-variant-east-asian': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-east-asian
+    types: [
+      { type: 'discrete', options: [ [ 'full-width', 'proportional-width' ] ] }
+    ]
+  },
+  'font-variant-ligatures': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-ligatures
+    types: [
+      { type: 'discrete',
+        options: [ [ 'common-ligatures', 'no-common-ligatures' ] ] }
+    ]
+  },
+  'font-variant-numeric': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-numeric
+    types: [
+      { type: 'discrete', options: [ [ 'lining-nums', 'oldstyle-nums' ] ] }
+    ]
+  },
+  'font-variant-position': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-position
+    types: [
+      { type: 'discrete', options: [ [ 'sub', 'super' ] ] }
+    ]
+  },
+  'font-weight': {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-weight
+    types: [
+    ]
+  },
+  'grid-auto-columns': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-columns
+    types: [
+      { type: 'discrete', options: [ [ '1px', '5px' ] ] }
+    ]
+  },
+  'grid-auto-flow': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow
+    types: [
+      { type: 'discrete', options: [ [ 'row', 'column' ] ] }
+    ]
+  },
+  'grid-auto-rows': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-rows
+    types: [
+      { type: 'discrete', options: [ [ '1px', '5px' ] ] }
+    ]
+  },
+  'grid-column-end': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-column-end
+    types: [
+      { type: 'discrete', options: [ [ '1', '5' ] ] }
+    ]
+  },
+  'grid-column-gap': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-column-gap
+    types: [
+    ]
+  },
+  'grid-column-start': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-column-start
+    types: [
+      { type: 'discrete', options: [ [ '1', '5' ] ] }
+    ]
+  },
+  'grid-row-end': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-row-end
+    types: [
+      { type: 'discrete', options: [ [ '1', '5' ] ] }
+    ]
+  },
+  'grid-row-gap': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-row-gap
+    types: [
+    ]
+  },
+  'grid-row-start': {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-row-start
+    types: [
+      { type: 'discrete', options: [ [ '1', '5' ] ] }
+    ]
+  },
+  'grid-template-areas': {
+    // https://drafts.csswg.org/css-template/#grid-template-areas
+    types: [
+      { type: 'discrete', options: [ [ '". . a b" ". .a b"', 'none' ] ] }
+    ]
+  },
+  'grid-template-columns': {
+    // https://drafts.csswg.org/css-template/#grid-template-columns
+    types: [
+      { type: 'discrete', options: [ [ '1px', '5px' ] ] }
+    ]
+  },
+  'grid-template-rows': {
+    // https://drafts.csswg.org/css-template/#grid-template-rows
+    types: [
+      { type: 'discrete', options: [ [ '1px', '5px' ] ] }
+    ]
+  },
+  'height': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-height
+    types: [
+    ]
+  },
+  'hyphens': {
+    // https://drafts.csswg.org/css-text-3/#propdef-hyphens
+    types: [
+      { type: 'discrete', options: [ [ 'manual', 'auto' ] ] }
+    ]
+  },
+  'image-orientation': {
+    // https://drafts.csswg.org/css-images-3/#propdef-image-orientation
+    types: [
+      { type: 'discrete', options: [ [ '0deg', '90deg' ] ] }
+    ]
+  },
+  'image-rendering': {
+    // https://drafts.csswg.org/css-images-3/#propdef-image-rendering
+    types: [
+    ]
+  },
+  'ime-mode': {
+    // https://drafts.csswg.org/css-ui/#input-method-editor
+    types: [
+      { type: 'discrete', options: [ [ 'disabled', 'auto' ] ] }
+    ]
+  },
+  'initial-letter': {
+    // https://drafts.csswg.org/css-inline/#propdef-initial-letter
+    types: [
+      { type: 'discrete', options: [ [ '1 2', '3 4' ] ] }
+    ]
+  },
+  'inline-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-inline-size
+    types: [
+    ]
+  },
+  'isolation': {
+    // https://drafts.fxtf.org/compositing-1/#propdef-isolation
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'isolate' ] ] }
+    ]
+  },
+  'justify-content': {
+    // https://drafts.csswg.org/css-align/#propdef-justify-content
+    types: [
+      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+    ]
+  },
+  'justify-items': {
+    // https://drafts.csswg.org/css-align/#propdef-justify-items
+    types: [
+      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+    ]
+  },
+  'justify-self': {
+    // https://drafts.csswg.org/css-align/#propdef-justify-self
+    types: [
+      { type: 'discrete', options: [ [ 'baseline', 'last baseline' ] ] }
+    ]
+  },
+  'left': {
+    // https://drafts.csswg.org/css-position/#propdef-left
+    types: [
+    ]
+  },
+  'letter-spacing': {
+    // https://drafts.csswg.org/css-text-3/#propdef-letter-spacing
+    types: [ 'length' ]
+  },
+  'lighting-color': {
+    // https://drafts.fxtf.org/filters/#LightingColorProperty
+    types: [ 'color' ]
+  },
+  'line-height': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-line-height
+    types: [
+    ]
+  },
+  'list-style-image': {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-image
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'list-style-position': {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-position
+    types: [
+      { type: 'discrete', options: [ [ 'inside', 'outside' ] ] }
+    ]
+  },
+  'list-style-type': {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-type
+    types: [
+      { type: 'discrete', options: [ [ 'circle', 'square' ] ] }
+    ]
+  },
+  'margin-block-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-margin-block-end
+    types: [
+    ]
+  },
+  'margin-block-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-margin-block-start
+    types: [
+    ]
+  },
+  'margin-bottom': {
+    // https://drafts.csswg.org/css-box/#propdef-margin-bottom
+    types: [
+    ]
+  },
+  'margin-inline-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-margin-inline-end
+    types: [
+    ]
+  },
+  'margin-inline-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-margin-inline-start
+    types: [
+    ]
+  },
+  'margin-left': {
+    // https://drafts.csswg.org/css-box/#propdef-margin-left
+    types: [
+    ]
+  },
+  'margin-right': {
+    // https://drafts.csswg.org/css-box/#propdef-margin-right
+    types: [
+    ]
+  },
+  'margin-top': {
+    // https://drafts.csswg.org/css-box/#propdef-margin-top
+    types: [
+    ]
+  },
+  'marker-end': {
+    // https://svgwg.org/specs/markers/#MarkerEndProperty
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'marker-mid': {
+    // https://svgwg.org/specs/markers/#MarkerMidProperty
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'marker-start': {
+    // https://svgwg.org/specs/markers/#MarkerStartProperty
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'mask': {
+    // https://drafts.fxtf.org/css-masking-1/#the-mask
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'mask-clip': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
+    types: [
+      { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] }
+    ]
+  },
+  'mask-composite': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite
+    types: [
+      { type: 'discrete', options: [ [ 'add', 'subtract' ] ] }
+    ]
+  },
+  'mask-image': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-image
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'mask-mode': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode
+    types: [
+      { type: 'discrete', options: [ [ 'alpha', 'luminance' ] ] }
+    ]
+  },
+  'mask-origin': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin
+    types: [
+      { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] }
+    ]
+  },
+  'mask-position': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-position
+    types: [
+    ]
+  },
+  'mask-position-x': {
+    // https://lists.w3.org/Archives/Public/www-style/2014Jun/0166.html
+    types: [
+    ]
+  },
+  'mask-position-y': {
+    // https://lists.w3.org/Archives/Public/www-style/2014Jun/0166.html
+    types: [
+    ]
+  },
+  'mask-repeat': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat
+    types: [
+      { type: 'discrete', options: [ [ 'space', 'round' ] ] }
+    ]
+  },
+  'mask-size': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-size
+    types: [
+    ]
+  },
+  'mask-type': {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-type
+    types: [
+      { type: 'discrete', options: [ [ 'alpha', 'luminance' ] ] }
+    ]
+  },
+  'max-block-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-max-block-size
+    types: [
+    ]
+  },
+  'max-height': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-max-height
+    types: [
+    ]
+  },
+  'max-inline-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-max-inline-size
+    types: [
+    ]
+  },
+  'max-width': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-max-width
+    types: [
+    ]
+  },
+  'min-block-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-min-block-size
+    types: [
+    ]
+  },
+  'min-height': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-min-height
+    types: [
+    ]
+  },
+  'min-inline-size': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-min-inline-size
+    types: [
+    ]
+  },
+  'min-width': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-min-width
+    types: [
+    ]
+  },
+  'mix-blend-mode': {
+    // https://drafts.fxtf.org/compositing-1/#propdef-mix-blend-mode
+    types: [
+      { type: 'discrete', options: [ [ 'multiply', 'screen' ] ] }
+    ]
+  },
+  'object-fit': {
+    // https://drafts.csswg.org/css-images-3/#propdef-object-fit
+    types: [
+      { type: 'discrete', options: [ [ 'fill', 'contain' ] ] }
+    ]
+  },
+  'object-position': {
+    // https://drafts.csswg.org/css-images-3/#propdef-object-position
+    types: [
+    ]
+  },
+  'offset-block-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-offset-block-end
+    types: [
+    ]
+  },
+  'offset-block-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-offset-block-start
+    types: [
+    ]
+  },
+  'offset-inline-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-offset-inline-end
+    types: [
+    ]
+  },
+  'offset-inline-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-offset-inline-start
+    types: [
+    ]
+  },
+  'opacity': {
+    // https://drafts.csswg.org/css-color/#propdef-opacity
+    types: [
+    ]
+  },
+  'order': {
+    // https://drafts.csswg.org/css-flexbox/#propdef-order
+    types: [ 'integer' ]
+  },
+  'outline-color': {
+    // https://drafts.csswg.org/css-ui-3/#propdef-outline-color
+    types: [ 'color' ]
+  },
+  'outline-offset': {
+    // https://drafts.csswg.org/css-ui-3/#propdef-outline-offset
+    types: [ 'length' ]
+  },
+  'outline-style': {
+    // https://drafts.csswg.org/css-ui/#propdef-outline-style
+    types: [
+      { type: 'discrete', options: [ [ 'none', 'dotted' ] ] }
+    ]
+  },
+  'outline-width': {
+    // https://drafts.csswg.org/css-ui-3/#propdef-outline-width
+    types: [ 'length' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.outlineStyle = 'solid';
+      return element;
+    }
+  },
+  'overflow': {
+    // https://drafts.csswg.org/css-overflow/#propdef-overflow
+    types: [
+    ]
+  },
+  'overflow-clip-box': {
+    // https://developer.mozilla.org/en/docs/Web/CSS/overflow-clip-box
+    types: [
+      { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] }
+    ]
+  },
+  'overflow-wrap': {
+    // https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap
+    types: [
+      { type: 'discrete', options: [ [ 'normal', 'break-word' ] ] }
+    ]
+  },
+  'overflow-x': {
+    // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-x
+    types: [
+      { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] }
+    ]
+  },
+  'overflow-y': {
+    // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-y
+    types: [
+      { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] }
+    ]
+  },
+  'padding-block-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-padding-block-end
+    types: [
+    ]
+  },
+  'padding-block-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-padding-block-start
+    types: [
+    ]
+  },
+  'padding-bottom': {
+    // https://drafts.csswg.org/css-box/#propdef-padding-bottom
+    types: [
+    ]
+  },
+  'padding-inline-end': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-padding-inline-end
+    types: [
+    ]
+  },
+  'padding-inline-start': {
+    // https://drafts.csswg.org/css-logical-props/#propdef-padding-inline-start
+    types: [
+    ]
+  },
+  'padding-left': {
+    // https://drafts.csswg.org/css-box/#propdef-padding-left
+    types: [
+    ]
+  },
+  'padding-right': {
+    // https://drafts.csswg.org/css-box/#propdef-padding-right
+    types: [
+    ]
+  },
+  'padding-top': {
+    // https://drafts.csswg.org/css-box/#propdef-padding-top
+    types: [
+    ]
+  },
+  'page-break-after': {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-after
+    types: [
+      { type: 'discrete', options: [ [ 'always', 'auto' ] ] }
+    ]
+  },
+  'page-break-before': {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-before
+    types: [
+      { type: 'discrete', options: [ [ 'always', 'auto' ] ] }
+    ]
+  },
+  'page-break-inside': {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-inside
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'avoid' ] ] }
+    ]
+  },
+  'paint-order': {
+    // https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty
+    types: [
+      { type: 'discrete', options: [ [ 'fill', 'stroke' ] ] }
+    ]
+  },
+  'perspective': {
+    // https://drafts.csswg.org/css-transforms-1/#propdef-perspective
+    types: [ 'length' ]
+  },
+  'perspective-origin': {
+    // https://drafts.csswg.org/css-transforms-1/#propdef-perspective-origin
+    types: [
+    ]
+  },
+  'pointer-events': {
+    // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
+    types: [
+      { type: 'discrete', options: [ [ 'fill', 'none' ] ] }
+    ]
+  },
+  'position': {
+    // https://drafts.csswg.org/css-position/#propdef-position
+    types: [
+      { type: 'discrete', options: [ [ 'absolute', 'fixed' ] ] }
+    ]
+  },
+  'quotes': {
+    // https://drafts.csswg.org/css-content-3/#propdef-quotes
+    types: [
+      { type: 'discrete', options: [ [ '"“" "”" "‘" "’"', '"‘" "’" "“" "”"' ] ] }
+    ]
+  },
+  'resize': {
+    // https://drafts.csswg.org/css-ui/#propdef-resize
+    types: [
+      { type: 'discrete', options: [ [ 'both', 'horizontal' ] ] }
+    ]
+  },
+  'right': {
+    // https://drafts.csswg.org/css-position/#propdef-right
+    types: [
+    ]
+  },
+  'ruby-align': {
+    // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align
+    types: [
+      { type: 'discrete', options: [ [ 'start', 'center' ] ] }
+    ]
+  },
+  'ruby-position': {
+    // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-position
+    types: [
+      { type: 'discrete', options: [ [ 'under', 'over' ] ] }
+    ],
+    setup: t => {
+      return createElement(t, 'ruby');
+    }
+  },
+  'scroll-behavior': {
+    // https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'smooth' ] ] }
+    ]
+  },
+  'scroll-snap-type-x': {
+    // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-x
+    types: [
+      { type: 'discrete', options: [ [ 'mandatory', 'proximity' ] ] }
+    ]
+  },
+  'scroll-snap-type-y': {
+    // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-y
+    types: [
+      { type: 'discrete', options: [ [ 'mandatory', 'proximity' ] ] }
+    ]
+  },
+  'shape-outside': {
+    // http://dev.w3.org/csswg/css-shapes/#propdef-shape-outside
+    types: [
+      { type: 'discrete',
+        options: [ [ 'url("http://localhost/test-1")',
+                     'url("http://localhost/test-2")' ] ] }
+    ]
+  },
+  'shape-rendering': {
+    // https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty
+    types: [
+      { type: 'discrete', options: [ [ 'optimizeSpeed', 'crispEdges' ] ] }
+    ]
+  },
+  'stop-color': {
+    // https://svgwg.org/svg2-draft/pservers.html#StopColorProperty
+    types: [ 'color' ]
+  },
+  'stop-opacity': {
+    // https://svgwg.org/svg2-draft/pservers.html#StopOpacityProperty
+    types: [
+    ]
+  },
+  'stroke': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeProperty
+    types: [
+    ]
+  },
+  'stroke-dasharray': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeDasharrayProperty
+    types: [
+    ]
+  },
+  'stroke-dashoffset': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeDashoffsetProperty
+    types: [
+    ]
+  },
+  'stroke-linecap': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty
+    types: [
+      { type: 'discrete', options: [ [ 'round', 'square' ] ] }
+    ]
+  },
+  'stroke-linejoin': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty
+    types: [
+      { type: 'discrete', options: [ [ 'round', 'miter' ] ] }
+    ],
+    setup: t => {
+      return createElement(t, 'rect');
+    }
+  },
+  'stroke-miterlimit': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeMiterlimitProperty
+    types: [
+    ]
+  },
+  'stroke-opacity': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeOpacityProperty
+    types: [
+    ]
+  },
+  'stroke-width': {
+    // https://svgwg.org/svg2-draft/painting.html#StrokeWidthProperty
+    types: [
+    ]
+  },
+  'table-layout': {
+    // https://drafts.csswg.org/css-tables/#propdef-table-layout
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'fixed' ] ] }
+    ]
+  },
+  'text-align': {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-align
+    types: [
+      { type: 'discrete', options: [ [ 'start', 'end' ] ] }
+    ]
+  },
+  'text-align-last': {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-align-last
+    types: [
+      { type: 'discrete', options: [ [ 'start', 'end' ] ] }
+    ]
+  },
+  'text-anchor': {
+    // https://svgwg.org/svg2-draft/text.html#TextAnchorProperty
+    types: [
+      { type: 'discrete', options: [ [ 'middle', 'end' ] ] }
+    ]
+  },
+  'text-combine-upright': {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright
+    types: [
+      { type: 'discrete', options: [ [ 'all', 'none' ] ] }
+    ]
+  },
+  'text-decoration-color': {
+    // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-color
+    types: [ 'color' ]
+  },
+  'text-decoration-line': {
+    // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-line
+    types: [
+      { type: 'discrete', options: [ [ 'underline', 'overline' ] ] }
+    ]
+  },
+  'text-decoration-style': {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-decoration-style
+    types: [
+      { type: 'discrete', options: [ [ 'solid', 'dotted' ] ] }
+    ]
+  },
+  'text-emphasis-color': {
+    // https://drafts.csswg.org/css-text-decor-3/#propdef-text-emphasis-color
+    types: [ 'color' ]
+  },
+  'text-emphasis-position': {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-position
+    types: [
+      { type: 'discrete', options: [ [ 'over right', 'under left' ] ] }
+    ]
+  },
+  'text-emphasis-style': {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-style
+    types: [
+      { type: 'discrete', options: [ [ 'filled circle', 'open dot' ] ] }
+    ]
+  },
+  'text-indent': {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-indent
+    types: [
+    ]
+  },
+  'text-orientation': {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation
+    types: [
+      { type: 'discrete', options: [ [ 'upright', 'sideways' ] ] }
+    ]
+  },
+  'text-overflow': {
+    // https://drafts.csswg.org/css-ui/#propdef-text-overflow
+    types: [
+      { type: 'discrete', options: [ [ 'clip', 'ellipsis' ] ] }
+    ]
+  },
+  'text-rendering': {
+    // https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
+    types: [
+      { type: 'discrete', options: [ [ 'optimizeSpeed', 'optimizeLegibility' ] ] }
+    ]
+  },
+  'text-shadow': {
+    // https://drafts.csswg.org/css-text-decor-3/#propdef-text-shadow
+    types: [ 'textShadowList' ],
+    setup: t => {
+      var element = createElement(t);
+      element.style.color = 'green';
+      return element;
+    }
+  },
+  'text-transform': {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-transform
+    types: [
+      { type: 'discrete', options: [ [ 'capitalize', 'uppercase' ] ] }
+    ]
+  },
+  'touch-action': {
+    // https://w3c.github.io/pointerevents/#the-touch-action-css-property
+    types: [
+      { type: 'discrete', options: [ [ 'auto', 'none' ] ] }
+    ]
+  },
+  'top': {
+    // https://drafts.csswg.org/css-position/#propdef-top
+    types: [
+    ]
+  },
+  'transform': {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform
+    types: [ 'transformList' ]
+  },
+  'transform-box': {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform-box
+    types: [
+      { type: 'discrete', options: [ [ 'fill-box', 'border-box' ] ] }
+    ]
+  },
+  'transform-origin': {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform-origin
+    types: [
+    ]
+  },
+  'transform-style': {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform-style
+    types: [
+      { type: 'discrete', options: [ [ 'flat', 'preserve-3d' ] ] }
+    ]
+  },
+  'unicode-bidi': {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-unicode-bidi
+    types: [
+      { type: 'discrete', options: [ [ 'embed', 'bidi-override' ] ] },
+    ]
+  },
+  'vector-effect': {
+    // https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty
+    types: [
+      { type: 'discrete', options: [ [ 'none', 'non-scaling-stroke' ] ] },
+    ]
+  },
+  'vertical-align': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-vertical-align
+    types: [
+    ]
+  },
+  'visibility': {
+    // https://drafts.csswg.org/css2/visufx.html#propdef-visibility
+    types: [ 'visibility' ]
+  },
+  'white-space': {
+    // https://drafts.csswg.org/css-text-4/#propdef-white-space
+    types: [
+      { type: 'discrete', options: [ [ 'pre', 'nowrap' ] ] }
+    ]
+  },
+  'width': {
+    // https://drafts.csswg.org/css21/visudet.html#propdef-width
+    types: [
+    ]
+  },
+  'word-break': {
+    // https://drafts.csswg.org/css-text-3/#propdef-word-break
+    types: [
+      { type: 'discrete', options: [ [ 'keep-all', 'break-all' ] ] }
+    ]
+  },
+  'word-spacing': {
+    // https://drafts.csswg.org/css-text-3/#propdef-word-spacing
+    types: [
+    ]
+  },
+  'will-change': {
+    // http://dev.w3.org/csswg/css-will-change/#propdef-will-change
+    types: [
+      { type: 'discrete', options: [ [ 'scroll-position', 'contents' ] ] }
+    ]
+  },
+  'writing-mode': {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode
+    types: [
+      { type: 'discrete', options: [ [ 'vertical-rl', 'sideways-rl' ] ] }
+    ]
+  },
+  'z-index': {
+    // https://drafts.csswg.org/css-position/#propdef-z-index
+    types: [
+    ]
+  },
+};
+
+function testAnimationSamples(animation, idlName, testSamples) {
+  var type = animation.effect.target.type;
+  var target = type
+               ? animation.effect.target.parentElement
+               : animation.effect.target;
+  testSamples.forEach(function(testSample) {
+    animation.currentTime = testSample.time;
+    assert_equals(getComputedStyle(target, type)[idlName],
+                  testSample.expected,
+                  'The value should be ' + testSample.expected +
+                  ' at ' + testSample.time + 'ms');
+  });
+}
+
+function testAnimationSampleMatrices(animation, idlName, testSamples) {
+  var target = animation.effect.target;
+  testSamples.forEach(function(testSample) {
+    animation.currentTime = testSample.time;
+    var actual = getComputedStyle(target)[idlName];
+    var expected = createMatrixFromArray(testSample.expected);
+    assert_matrix_equals(actual, expected,
+                         'The value should be ' + expected +
+                         ' at ' + testSample.time + 'ms but got ' + actual);
+  });
+}
+
+function createTestElement(t, setup) {
+  return setup ? setup(t) : createElement(t);
+}
+
+function isSupported(property) {
+  var testKeyframe = new TestKeyframe(propertyToIDL(property));
+  try {
+    // Since TestKeyframe returns 'undefined' for |property|,
+    // the KeyframeEffect constructor will throw
+    // if the string 'undefined' is not a valid value for the property.
+    new KeyframeEffect(null, testKeyframe);
+  } catch(e) {}
+  return testKeyframe.propAccessCount !== 0;
+}
+
+function TestKeyframe(testProp) {
+  var _propAccessCount = 0;
+
+  Object.defineProperty(this, testProp, {
+    get: function() { _propAccessCount++; },
+    enumerable: true
+  });
+
+  Object.defineProperty(this, 'propAccessCount', {
+    get: function() { return _propAccessCount; }
+  });
+}
+
+function propertyToIDL(property) {
+  // https://w3c.github.io/web-animations/#animation-property-name-to-idl-attribute-name
+  if (property === 'float') {
+    return 'cssFloat';
+  }
+  return property.replace(/-[a-z]/gi,
+                          function (str) {
+                            return str.substr(1).toUpperCase(); });
+}
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-types.js b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-types.js
new file mode 100644
index 0000000..14c8f27
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/property-types.js
@@ -0,0 +1,972 @@
+const discreteType = {
+  testInterpolation: function(property, setup, options) {
+    options.forEach(function(keyframes) {
+      var [ from, to ] = keyframes;
+      test(function(t) {
+        var idlName = propertyToIDL(property);
+        var target = createTestElement(t, setup);
+        var animation = target.animate({ [idlName]: [from, to] },
+                                       { duration: 1000, fill: 'both' });
+        testAnimationSamples(animation, idlName,
+                             [{ time: 0,    expected: from.toLowerCase() },
+                              { time: 499,  expected: from.toLowerCase() },
+                              { time: 500,  expected: to.toLowerCase() },
+                              { time: 1000, expected: to.toLowerCase() }]);
+      }, property + ' uses discrete animation when animating between "'
+         + from + '" and "' + to + '" with linear easing');
+
+      test(function(t) {
+        // Easing: http://cubic-bezier.com/#.68,0,1,.01
+        // With this curve, we don't reach the 50% point until about 95% of
+        // the time has expired.
+        var idlName = propertyToIDL(property);
+        var keyframes = {};
+        var target = createTestElement(t, setup);
+        var animation = target.animate({ [idlName]: [from, to] },
+                                       { duration: 1000, fill: 'both',
+                                         easing: 'cubic-bezier(0.68,0,1,0.01)' });
+        testAnimationSamples(animation, idlName,
+                             [{ time: 0,    expected: from.toLowerCase() },
+                              { time: 940,  expected: from.toLowerCase() },
+                              { time: 960,  expected: to.toLowerCase() }]);
+      }, property + ' uses discrete animation when animating between "'
+         + from + '" and "' + to + '" with effect easing');
+
+      test(function(t) {
+        // Easing: http://cubic-bezier.com/#.68,0,1,.01
+        // With this curve, we don't reach the 50% point until about 95% of
+        // the time has expired.
+        var idlName = propertyToIDL(property);
+        var target = createTestElement(t, setup);
+        var animation = target.animate({ [idlName]: [from, to],
+                                         easing: 'cubic-bezier(0.68,0,1,0.01)' },
+                                       { duration: 1000, fill: 'both' });
+        testAnimationSamples(animation, idlName,
+                             [{ time: 0,    expected: from.toLowerCase() },
+                              { time: 940,  expected: from.toLowerCase() },
+                              { time: 960,  expected: to.toLowerCase() }]);
+      }, property + ' uses discrete animation when animating between "'
+         + from + '" and "' + to + '" with keyframe easing');
+    });
+  },
+
+  testAddition: function(property, setup, options) {
+    options.forEach(function(keyframes) {
+      var [ from, to ] = keyframes;
+      test(function(t) {
+        var idlName = propertyToIDL(property);
+        var target = createTestElement(t, setup);
+        target.animate({ [idlName]: [from, from] }, 1000);
+        var animation = target.animate({ [idlName]: [to, to] },
+                                       { duration: 1000, composite: 'add' });
+        testAnimationSamples(animation, idlName,
+                             [{ time: 0, expected: to.toLowerCase() }]);
+      }, property + ': "' + to + '" onto "' + from + '"');
+
+      test(function(t) {
+        var idlName = propertyToIDL(property);
+        var target = createTestElement(t, setup);
+        target.animate({ [idlName]: [to, to] }, 1000);
+        var animation = target.animate({ [idlName]: [from, from] },
+                                       { duration: 1000, composite: 'add' });
+        testAnimationSamples(animation, idlName,
+                             [{ time: 0, expected: from.toLowerCase() }]);
+      }, property + ': "' + from + '" onto "' + to + '"');
+    });
+  },
+
+};
+
+const lengthType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10px', '50px'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px' },
+                            { time: 500,  expected: '30px' },
+                            { time: 1000, expected: '50px' }]);
+    }, property + ' supports animating as a length');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['1rem', '5rem'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px' },
+                            { time: 500,  expected: '30px' },
+                            { time: 1000, expected: '50px' }]);
+    }, property + ' supports animating as a length of rem');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10px';
+      var animation = target.animate({ [idlName]: ['10px', '50px'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px' }]);
+    }, property + ': length');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '1rem';
+      var animation = target.animate({ [idlName]: ['1rem', '5rem'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px' }]);
+    }, property + ': length of rem');
+  },
+
+};
+
+const percentageType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10%', '50%'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10%' },
+                            { time: 500,  expected: '30%' },
+                            { time: 1000, expected: '50%' }]);
+    }, property + ' supports animating as a percentage');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '60%';
+      var animation = target.animate({ [idlName]: ['70%', '100%'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '130%' }]);
+    }, property + ': percentage');
+  },
+
+};
+
+const integerType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: [-2, 2] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '-2' },
+                            { time: 500,  expected: '0' },
+                            { time: 1000, expected: '2' }]);
+    }, property + ' supports animating as an integer');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = -1;
+      var animation = target.animate({ [idlName]: [-2, 2] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '-3' }]);
+    }, property + ': integer');
+  },
+
+};
+
+const lengthPercentageOrCalcType = {
+  testInterpolation: function(property, setup) {
+    lengthType.testInterpolation(property, setup);
+    percentageType.testInterpolation(property, setup);
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10px', '20%'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px' },
+                            { time: 500,  expected: 'calc(5px + 10%)' },
+                            { time: 1000, expected: '20%' }]);
+    }, property + ' supports animating as combination units "px" and "%"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10%', '2em'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10%' },
+                            { time: 500,  expected: 'calc(10px + 5%)' },
+                            { time: 1000, expected: '20px' }]);
+    }, property + ' supports animating as combination units "%" and "em"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['1em', '2rem'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px' },
+                            { time: 500,  expected: '15px' },
+                            { time: 1000, expected: '20px' }]);
+    }, property + ' supports animating as combination units "em" and "rem"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10px', 'calc(1em + 20%)'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px' },
+                            { time: 500,  expected: 'calc(10px + 10%)' },
+                            { time: 1000, expected: 'calc(10px + 20%)' }]);
+    }, property + ' supports animating as combination units "px" and "calc"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate(
+        { [idlName]: ['calc(10px + 10%)', 'calc(1em + 1rem + 20%)'] },
+        { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,
+                              expected: 'calc(10px + 10%)' },
+                            { time: 500,
+                              expected: 'calc(15px + 15%)' },
+                            { time: 1000,
+                              expected: 'calc(20px + 20%)' }]);
+    }, property + ' supports animating as a calc');
+  },
+
+  testAddition: function(property, setup) {
+    lengthType.testAddition(property, setup);
+    percentageType.testAddition(property, setup);
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10px';
+      var animation = target.animate({ [idlName]: ['10%', '50%'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(10px + 10%)' }]);
+    }, property + ': units "%" onto "px"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10%';
+      var animation = target.animate({ [idlName]: ['10px', '50px'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(10px + 10%)' }]);
+    }, property + ': units "px" onto "%"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10%';
+      var animation = target.animate({ [idlName]: ['2rem', '5rem'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(20px + 10%)' }]);
+    }, property + ': units "rem" onto "%"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '2rem';
+      var animation = target.animate({ [idlName]: ['10%', '50%'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(20px + 10%)' }]);
+    }, property + ': units "%" onto "rem"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '2em';
+      var animation = target.animate({ [idlName]: ['2rem', '5rem'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '40px' }]);
+    }, property + ': units "rem" onto "em"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '2rem';
+      var animation = target.animate({ [idlName]: ['2em', '5em'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '40px' }]);
+    }, property + ': units "em" onto "rem"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10px';
+      var animation = target.animate({ [idlName]: ['calc(2em + 20%)',
+                                                   'calc(5rem + 50%)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(30px + 20%)' }]);
+    }, property + ': units "calc" onto "px"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'calc(10px + 10%)';
+      var animation = target.animate({ [idlName]: ['calc(20px + 20%)',
+                                                   'calc(2em + 3rem + 40%)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0, expected: 'calc(30px + 30%)' }]);
+    }, property + ': calc');
+  },
+
+};
+
+const positiveNumberType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: [1.1, 1.5] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '1.1' },
+                            { time: 500,  expected: '1.3' },
+                            { time: 1000, expected: '1.5' }]);
+    }, property + ' supports animating as a positive number');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 1.1;
+      var animation = target.animate({ [idlName]: [1.1, 1.5] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '2.2' }]);
+    }, property + ': positive number');
+  },
+
+};
+
+const visibilityType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['visible', 'hidden'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'visible' },
+                            { time: 999,  expected: 'visible' },
+                            { time: 1000, expected: 'hidden' }]);
+    }, property + ' uses visibility animation when animating '
+       + 'from "visible" to "hidden"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['hidden', 'visible'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'hidden' },
+                            { time: 1,    expected: 'visible' },
+                            { time: 1000, expected: 'visible' }]);
+    }, property + ' uses visibility animation when animating '
+     + 'from "hidden" to "visible"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['hidden', 'collapse'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'hidden' },
+                            { time: 499,  expected: 'hidden' },
+                            { time: 500,  expected: 'collapse' },
+                            { time: 1000, expected: 'collapse' }]);
+    }, property + ' uses visibility animation when animating '
+     + 'from "hidden" to "collapse"');
+
+    test(function(t) {
+      // Easing: http://cubic-bezier.com/#.68,-.55,.26,1.55
+      // With this curve, the value is less than 0 till about 34%
+      // also more than 1 since about 63%
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =
+        target.animate({ [idlName]: ['visible', 'hidden'] },
+                       { duration: 1000, fill: 'both',
+                         easing: 'cubic-bezier(0.68, -0.55, 0.26, 1.55)' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'visible' },
+                            { time: 1,    expected: 'visible' },
+                            { time: 330,  expected: 'visible' },
+                            { time: 340,  expected: 'visible' },
+                            { time: 620,  expected: 'visible' },
+                            { time: 630,  expected: 'hidden' },
+                            { time: 1000, expected: 'hidden' }]);
+    }, property + ' uses visibility animation when animating '
+     + 'from "visible" to "hidden" with easeInOutBack easing');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'visible';
+      var animation = target.animate({ [idlName]: ['visible', 'hidden'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'visible' },
+                            { time: 1000, expected: 'visible' }]);
+    }, property + ': onto "visible"');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'hidden';
+      var animation = target.animate({ [idlName]: ['hidden', 'visible'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: 'hidden' },
+                            { time: 1000, expected: 'visible' }]);
+    }, property + ': onto "hidden"');
+  },
+
+};
+
+const colorType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['rgb(255, 0, 0)',
+                                                   'rgb(0, 0, 255)'] },
+                                     1000);
+      testAnimationSamples(animation, idlName,
+                           [{ time: 500,  expected: 'rgb(128, 0, 128)' }]);
+    }, property + ' supports animating as color of rgb()');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['#ff0000', '#0000ff'] },
+                                     1000);
+      testAnimationSamples(animation, idlName,
+                           [{ time: 500,  expected: 'rgb(128, 0, 128)' }]);
+    }, property + ' supports animating as color of #RGB');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['hsl(0,   100%, 50%)',
+                                                   'hsl(240, 100%, 50%)'] },
+                                     1000);
+      testAnimationSamples(animation, idlName,
+                           [{ time: 500,  expected: 'rgb(128, 0, 128)' }]);
+    }, property + ' supports animating as color of hsl()');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['#ff000066', '#0000ffcc'] },
+                                     1000);
+                                             // R: 255 * (0.4 * 0.5) / 0.6 = 85
+                                             // G: 255 * (0.8 * 0.5) / 0.6 = 170
+      testAnimationSamples(animation, idlName,
+                           [{ time: 500,  expected: 'rgba(85, 0, 170, 0.6)' }]);
+    }, property + ' supports animating as color of #RGBa');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['rgba(255, 0, 0, 0.4)',
+                                                   'rgba(0, 0, 255, 0.8)'] },
+                                     1000);
+      testAnimationSamples(animation, idlName,      // Same as above.
+                           [{ time: 500,  expected: 'rgba(85, 0, 170, 0.6)' }]);
+    }, property + ' supports animating as color of rgba()');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['hsla(0,   100%, 50%, 0.4)',
+                                                   'hsla(240, 100%, 50%, 0.8)'] },
+                                     1000);
+      testAnimationSamples(animation, idlName,      // Same as above.
+                           [{ time: 500,  expected: 'rgba(85, 0, 170, 0.6)' }]);
+    }, property + ' supports animating as color of hsla()');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['rgb(255, 0, 0)',
+                                                   'rgb(0, 0, 255)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,   expected: 'rgb(255, 128, 128)' },
+                            // The value at 50% is interpolated
+                            // from 'rgb(128+255, 128, 128)'
+                            // to   'rgb(128,     128, 128+255)'.
+                            { time: 500, expected: 'rgb(255, 128, 255)' }]);
+    }, property + ' supports animating as color of rgb() with overflowed ' +
+       'from and to values');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['#ff0000', '#0000ff'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,  expected: 'rgb(255, 128, 128)' }]);
+    }, property + ' supports animating as color of #RGB');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['hsl(0,   100%, 50%)',
+                                                   'hsl(240, 100%, 50%)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,  expected: 'rgb(255, 128, 128)' }]);
+    }, property + ' supports animating as color of hsl()');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['#ff000066', '#0000ffcc'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,  expected: 'rgb(230, 128, 128)' }]);
+    }, property + ' supports animating as color of #RGBa');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['rgba(255, 0, 0, 0.4)',
+                                                   'rgba(0, 0, 255, 0.8)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,      // Same as above.
+                           [{ time: 0,  expected: 'rgb(230, 128, 128)' }]);
+    }, property + ' supports animating as color of rgba()');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(128, 128, 128)';
+      var animation = target.animate({ [idlName]: ['hsla(0,   100%, 50%, 0.4)',
+                                                   'hsla(240, 100%, 50%, 0.8)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,      // Same as above.
+                           [{ time: 0,  expected: 'rgb(230, 128, 128)' }]);
+    }, property + ' supports animating as color of hsla()');
+  },
+
+};
+
+const transformListType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['translate(200px, -200px)',
+                                                   'translate(400px, 400px)'] },
+                                     1000);
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500,  expected: [ 1, 0, 0, 1, 300, 100 ] }]);
+    }, property + ': translate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['rotate(45deg)',
+                                                   'rotate(135deg)'] },
+                                     1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500,  expected: [ Math.cos(Math.PI / 2),
+                                   Math.sin(Math.PI / 2),
+                                  -Math.sin(Math.PI / 2),
+                                   Math.cos(Math.PI / 2),
+                                   0, 0] }]);
+    }, property + ': rotate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['scale(3)', 'scale(5)'] },
+                                     1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500,  expected: [ 4, 0, 0, 4, 0, 0 ] }]);
+    }, property + ': scale');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['skew(30deg, 60deg)',
+                                                   'skew(60deg, 30deg)'] },
+                                     1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500,  expected: [ 1, Math.tan(Math.PI / 4),
+                                   Math.tan(Math.PI / 4), 1,
+                                   0, 0] }]);
+    }, property + ': skew');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =
+        target.animate({ [idlName]: ['translateX(100px) rotate(45deg)',
+                                     'translateX(200px) rotate(135deg)'] },
+                       1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500,  expected: [ Math.cos(Math.PI / 2),
+                                   Math.sin(Math.PI / 2),
+                                  -Math.sin(Math.PI / 2),
+                                   Math.cos(Math.PI / 2),
+                                   150, 0 ] }]);
+    }, property + ': rotate and translate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =
+        target.animate({ [idlName]: ['rotate(45deg) translateX(100px)',
+                                     'rotate(135deg) translateX(200px)'] },
+                       1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500, expected: [ Math.cos(Math.PI / 2),
+                                  Math.sin(Math.PI / 2),
+                                 -Math.sin(Math.PI / 2),
+                                  Math.cos(Math.PI / 2),
+                                  150 * Math.cos(Math.PI / 2),
+                                  150 * Math.sin(Math.PI / 2) ] }]);
+    }, property + ': translate and rotate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =                // matrix(0, 1, -1, 0, 0, 100)
+        target.animate({ [idlName]: ['rotate(90deg) translateX(100px)',
+                                     // matrix(-1, 0, 0, -1, 200, 0)
+                                     'translateX(200px) rotate(180deg)'] },
+                       1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500, expected: [ Math.cos(Math.PI * 3 / 4),
+                                  Math.sin(Math.PI * 3 / 4),
+                                 -Math.sin(Math.PI * 3 / 4),
+                                  Math.cos(Math.PI * 3 / 4),
+                                  100, 50 ] }]);
+    }, property + ': mismatch order of translate and rotate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =                 // Same matrices as above.
+        target.animate({ [idlName]: [ 'matrix(0, 1, -1, 0, 0, 100)',
+                                      'matrix(-1, 0, 0, -1, 200, 0)' ] },
+                       1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500, expected: [ Math.cos(Math.PI * 3 / 4),
+                                  Math.sin(Math.PI * 3 / 4),
+                                 -Math.sin(Math.PI * 3 / 4),
+                                  Math.cos(Math.PI * 3 / 4),
+                                  100, 50 ] }]);
+    }, property + ': matrix');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation =
+        target.animate({ [idlName]: [ 'rotate3d(1, 1, 0, 0deg)',
+                                      'rotate3d(1, 1, 0, 90deg)'] },
+                       1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500, expected: rotate3dToMatrix(1, 1, 0, Math.PI / 4) }]);
+    }, property + ': rotate3d');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      // To calculate expected matrices easily, generate input matrices from
+      // rotate3d.
+      var from = rotate3dToMatrix3d(1, 1, 0, Math.PI / 4);
+      var to = rotate3dToMatrix3d(1, 1, 0, Math.PI * 3 / 4);
+      var animation =
+        target.animate({ [idlName]: [ from, to ] }, 1000);
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 500, expected: rotate3dToMatrix(1, 1, 0, Math.PI * 2 / 4) }]);
+    }, property + ': matrix3d');
+
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'translateX(100px)';
+      var animation = target.animate({ [idlName]: ['translateX(-200px)',
+                                                   'translateX(500px)'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'add' });
+      testAnimationSampleMatrices(animation, idlName,
+        [ { time: 0,    expected: [ 1, 0, 0, 1, -100, 0 ] },
+          { time: 1000, expected: [ 1, 0, 0, 1,  600, 0 ] }]);
+    }, property + ': translate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rotate(45deg)';
+      var animation = target.animate({ [idlName]: ['rotate(-90deg)',
+                                                   'rotate(90deg)'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [ Math.cos(-Math.PI / 4),
+                                   Math.sin(-Math.PI / 4),
+                                  -Math.sin(-Math.PI / 4),
+                                   Math.cos(-Math.PI / 4),
+                                   0, 0] },
+         { time: 1000, expected: [ Math.cos(Math.PI * 3 / 4),
+                                   Math.sin(Math.PI * 3 / 4),
+                                  -Math.sin(Math.PI * 3 / 4),
+                                   Math.cos(Math.PI * 3 / 4),
+                                   0, 0] }]);
+    }, property + ': rotate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'scale(2)';
+      var animation = target.animate({ [idlName]: ['scale(-3)', 'scale(5)'] },
+                                     { duration: 1000, fill: 'both',
+                                       composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [ -6, 0, 0, -6, 0, 0 ] }, // scale(-3) scale(2)
+         { time: 1000, expected: [ 10, 0, 0, 10, 0, 0 ] }]); // scale(5) scale(2)
+    }, property + ': scale');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+                              // matrix(1, tan(10deg), tan(10deg), 1)
+      target.style[idlName] = 'skew(10deg, 10deg)';
+      var animation =                // matrix(1, tan(20deg), tan(-30deg), 1)
+        target.animate({ [idlName]: ['skew(-30deg, 20deg)',
+                                     // matrix(1, tan(-30deg), tan(20deg), 1)
+                                     'skew(20deg, -30deg)'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      // matrix at 0%.
+      // [ 1          tan(10deg) ] [ 1          tan(-30deg) ]
+      // [ tan(10deg)          1 ] [ tan(20deg)           1 ] =
+      //
+      // [ 1 + tan(10deg) * tan(20deg) tan(-30deg) + tan(10deg)     ]
+      // [     tan(10deg) + tan(20deg) tan(10deg) * tan(-30deg) + 1 ]
+
+      // matrix at 100%.
+      // [ 1          tan(10deg) ] [ 1           tan(20deg) ]
+      // [ tan(10deg)          1 ] [ tan(-30deg)          1 ] =
+      //
+      // [ 1 + tan(10deg) * tan(-30deg) tan(20deg) + tan(10deg)     ]
+      // [     tan(10deg) + tan(-30deg) tan(10deg) * tan(20deg) + 1 ]
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [ 1 + Math.tan(Math.PI/18) * Math.tan(Math.PI/9),
+                                   Math.tan(Math.PI/18) + Math.tan(Math.PI/9),
+                                   Math.tan(-Math.PI/6) + Math.tan(Math.PI/18),
+                                   1 + Math.tan(Math.PI/18) * Math.tan(-Math.PI/6),
+                                   0, 0] },
+         { time: 1000, expected: [ 1 + Math.tan(Math.PI/18) * Math.tan(-Math.PI/6),
+                                   Math.tan(Math.PI/18) + Math.tan(-Math.PI/6),
+                                   Math.tan(Math.PI/9) + Math.tan(Math.PI/18),
+                                   1 + Math.tan(Math.PI/18) * Math.tan(Math.PI/9),
+                                   0, 0] }]);
+    }, property + ': skew');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+                               // matrix(1, 0, 0, 1, 100, 0)
+      target.style[idlName] = 'translateX(100px)';
+      var animation =                // matrix(0, 1, -1, 0, 0, 0)
+        target.animate({ [idlName]: ['rotate(90deg)',
+                                     // matrix(-1, 0, 0, -1, 0, 0)
+                                     'rotate(180deg)'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [  0, 1, -1,  0, 100, 0 ] },
+         { time: 1000, expected: [ -1, 0,  0, -1, 100, 0 ] }]);
+    }, property + ': rotate on translate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+                               // matrix(0, 1, -1, 0, 0, 0)
+      target.style[idlName] = 'rotate(90deg)';
+      var animation =                // matrix(1, 0, 0, 1, 100, 0)
+        target.animate({ [idlName]: ['translateX(100px)',
+                                     // matrix(1, 0, 0, 1, 200, 0)
+                                     'translateX(200px)'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [ 0, 1, -1, 0, 0, 100 ] },
+         { time: 1000, expected: [ 0, 1, -1, 0, 0, 200 ] }]);
+    }, property + ': translate on rotate');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'matrix(0, 1, -1, 0, 0, 0)';
+      var animation =                 // Same matrices as above.
+        target.animate({ [idlName]: [ 'matrix(1, 0, 0, 1, 100, 0)',
+                                      'matrix(1, 0, 0, 1, 200, 0)' ] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: [ 0, 1, -1, 0, 0, 100 ] },
+         { time: 1000, expected: [ 0, 1, -1, 0, 0, 200 ] }]);
+    }, property + ': matrix');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rotate3d(1, 1, 0, 45deg)';
+      var animation =
+        target.animate({ [idlName]: [ 'rotate3d(1, 1, 0, -90deg)',
+                                      'rotate3d(1, 1, 0, 90deg)'] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: rotate3dToMatrix(1, 1, 0,    -Math.PI / 4) },
+         { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]);
+    }, property + ': rotate3d');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      // To calculate expected matrices easily, generate input matrices from
+      // rotate3d.
+      target.style[idlName] = rotate3dToMatrix3d(1, 1, 0, Math.PI / 4);
+      var from = rotate3dToMatrix3d(1, 1, 0, -Math.PI / 2);
+      var to = rotate3dToMatrix3d(1, 1, 0, Math.PI / 2);
+      var animation =
+        target.animate({ [idlName]: [ from, to ] },
+                       { duration: 1000, fill: 'both', composite: 'add' });
+
+      testAnimationSampleMatrices(animation, idlName,
+        [{ time: 0,    expected: rotate3dToMatrix(1, 1, 0,    -Math.PI / 4) },
+         { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]);
+    }, property + ': matrix3d');
+  },
+
+};
+
+const filterListType = {
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'blur(10px)';
+      var animation = target.animate({ [idlName]: ['blur(20px)',
+                                                   'blur(50px)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: 'blur(10px) blur(20px)' }]);
+    }, property + ': blur on blur');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'blur(10px)';
+      var animation = target.animate({ [idlName]: ['brightness(80%)',
+                                                   'brightness(40%)'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0,    expected: 'blur(10px) brightness(0.8)' }]);
+    }, property + ': different filter functions');
+  },
+};
+
+const textShadowListType = {
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(0, 0, 0) 0px 0px 0px';
+      var animation =
+        target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px',
+                                      'rgb(120, 120, 120) 10px 10px 10px'] },
+                       { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0, expected: 'rgb(0, 0, 0) 0px 0px 0px, ' +
+                               'rgb(120, 120, 120) 10px 10px 10px' }]);
+    }, property + ': shadow');
+  },
+};
+
+
+const boxShadowListType = {
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = 'rgb(0, 0, 0) 0px 0px 0px 0px';
+      var animation =
+        target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px 0px',
+                                      'rgb(120, 120, 120) 10px 10px 10px 0px'] },
+                       { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName,
+        [ { time: 0, expected: 'rgb(0, 0, 0) 0px 0px 0px 0px, ' +
+                               'rgb(120, 120, 120) 10px 10px 10px 0px' }]);
+    }, property + ': shadow');
+  },
+};
+
+const types = {
+  color: colorType,
+  discrete: discreteType,
+  filterList: filterListType,
+  integer: integerType,
+  length: lengthType,
+  percentage: percentageType,
+  lengthPercentageOrCalc: lengthPercentageOrCalcType,
+  positiveNumber: positiveNumberType,
+  transformList: transformListType,
+  visibility: visibilityType,
+  boxShadowList: boxShadowListType,
+  textShadowList: textShadowListType,
+};
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-filters.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-filters.html
new file mode 100644
index 0000000..11c865a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-filters.html
@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Keyframe spacing tests on filters</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#spacing-keyframes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+// Help function for testing the computed offsets by the distance array.
+function assert_animation_offsets(anim, dist) {
+  const frames = anim.effect.getKeyframes();
+  const cumDist = dist.reduce( (prev, curr) => {
+    prev.push(prev.length == 0 ? curr : curr + prev[prev.length - 1]);
+    return prev;
+  }, []);
+
+  const total = cumDist[cumDist.length - 1];
+  for (var i = 0; i < frames.length; ++i) {
+    assert_equals(frames[i].computedOffset, cumDist[i] / total,
+                  'computedOffset of frame ' + i);
+  }
+}
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'blur(1px)'},
+                           { filter: 'none' },  // The default value is 0px.
+                           { filter: 'blur(10px)' },
+                           { filter: 'blur(8px)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, 1, 10, (10 - 8) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on blur' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'brightness(50%)'},
+                           { filter: 'none' },  // The default value is 1.
+                           { filter: 'brightness(2)' },
+                           { filter: 'brightness(175%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, (1 - 0.5), (2 - 1), (2.0 - 1.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on brightness' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'contrast(50%)'},
+                           { filter: 'none' },  // The default value is 1.
+                           { filter: 'contrast(2)' },
+                           { filter: 'contrast(175%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, (1 - 0.5), (2 - 1), (2.0 - 1.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on contrast' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'drop-shadow(10px 10px 10px blue)'},
+                           { filter: 'none' },
+                     // none filter: 'drop-shadow(0, 0, 0, rgba(0, 0, 0, 0))'
+                           { filter: 'drop-shadow(5px 5px 1px black)' },
+                           { filter: 'drop-shadow(20px 20px yellow)' } ],
+                         { spacing: 'paced(filter)' });
+
+  // Blue:   rgba(0, 0, 255, 1.0)   = rgba(  0%,   0%, 100%, 100%).
+  // Black:  rgba(0, 0, 0, 1.0)     = rgba(  0%,   0%,   0%, 100%).
+  // Yellow: rgba(255, 255, 0, 1.0) = rgba(100%, 100%,   0%, 100%).
+  var dist = [ 0,
+               Math.sqrt(10 * 10 + 10 * 10 + 10 * 10 + (1 * 1 + 1 * 1)),
+               Math.sqrt(5 * 5 + 5 * 5 + 1 * 1 + (1 * 1)),
+               Math.sqrt(15 * 15 + 15 * 15 + 1 * 1 + (1 * 1 + 1 * 1)) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on drop-shadow' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'drop-shadow(10px 10px 10px)'},
+                           { filter: 'drop-shadow(0 0)' },
+                           { filter: 'drop-shadow(5px 5px 1px black)' },
+                           { filter: 'drop-shadow(20px 20px yellow)' } ],
+                         { spacing: 'paced(filter)' });
+
+  // Black:  rgba(0, 0, 0, 1.0)     = rgba(  0%,   0%, 0%, 100%).
+  // Yellow: rgba(255, 255, 0, 1.0) = rgba(100%, 100%, 0%, 100%).
+  var dist = [ 0,
+               Math.sqrt(10 * 10 + 10 * 10 + 10 * 10),
+               0, // The distance is zero because the 2nd frame uses no-color.
+               Math.sqrt(15 * 15 + 15 * 15 + 1 * 1 + (1 * 1 + 1 * 1)) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test getting zero distance when computing distance between ' +
+   'color and no-color on drop-shadow');
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'grayscale(50%)'},
+                           { filter: 'none' },  // The default value is 0.
+                             // Values of grayscale over 100% are clamped to 1.
+                           { filter: 'grayscale(2)' },
+                           { filter: 'grayscale(75%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, 0.5, 1, (1.0 - 0.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on grayscale' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'hue-rotate(180deg)'},
+                           { filter: 'none' },  // The default value is 0deg.
+                           { filter: 'hue-rotate(720deg)' },
+                           { filter: 'hue-rotate(-180deg)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, 180, 720, 720 + 180 ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on hue-rotate' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'invert(50%)'},
+                           { filter: 'none' },  // The default value is 0.
+                             // Values of invert over 100% are clamped to 1.
+                           { filter: 'invert(2)' },
+                           { filter: 'invert(75%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, 0.5, 1, (1.0 - 0.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on invert' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'opacity(50%)'},
+                           { filter: 'none' },  // The default value is 1.
+                             // Values of opacity over 100% are clamped to 1.
+                           { filter: 'opacity(2)' },
+                           { filter: 'opacity(75%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, (1 - 0.5), (1 - 1), (1.0 - 0.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on opacity' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'saturate(50%)'},
+                           { filter: 'none' },  // The default value is 1.
+                           { filter: 'saturate(2)' },
+                           { filter: 'saturate(175%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, (1 - 0.5), (2 - 1), (2.0 - 1.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on saturate' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'sepia(50%)'},
+                           { filter: 'none' },  // The default value is 0.
+                             // Values of sepia over 100% are clamped to 1.
+                           { filter: 'sepia(2)' },
+                           { filter: 'sepia(75%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0, 0.5, 1, (1.0 - 0.75) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on sepia' );
+
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'grayscale(50%) opacity(100%) blur(5px)' },
+                           { filter: 'none' },
+                     // none filter: 'grayscale(0) opacity(1) blur(0px)'
+                           { filter: 'grayscale(100%) opacity(50%) blur(1px)' },
+                           { filter: 'grayscale(75%) opacity(50%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0,
+               Math.sqrt(0.5 * 0.5 + 5 * 5),
+               Math.sqrt(1 * 1 + 0.5 * 0.5 + 1 * 1),
+               Math.sqrt(0.25 * 0.25 + 1 * 1) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on filter function lists' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { filter: 'grayscale(50%) opacity(100%)' },
+                           { filter: 'opacity(10%) grayscale(50%)' },
+                           { filter: 'grayscale(100%) opacity(50%) blur(1px)' },
+                           { filter: 'grayscale(75%) opacity(50%)' } ],
+                         { spacing: 'paced(filter)' });
+
+  var dist = [ 0,
+               0,
+               0,
+               Math.sqrt(0.25 * 0.25 + 1 * 1) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on filter function lists' );
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html
new file mode 100644
index 0000000..9f7cfae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-shapes.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Keyframe spacing tests on shapes</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#spacing-keyframes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+// Help function for testing the computed offsets by the distance array.
+function assert_animation_offsets(anim, dist) {
+  const frames = anim.effect.getKeyframes();
+  const cumDist = dist.reduce( (prev, curr) => {
+    prev.push(prev.length == 0 ? curr : curr + prev[prev.length - 1]);
+    return prev;
+  }, []);
+
+  const total = cumDist[cumDist.length - 1];
+  for (var i = 0; i < frames.length; ++i) {
+    assert_equals(frames[i].computedOffset, cumDist[i] / total,
+                  'computedOffset of frame ' + i);
+  }
+}
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { clipPath: 'circle(20px)' },
+                           { clipPath: 'ellipse(10px 20px)' },
+                           { clipPath: 'polygon(50px 0px, 100px 50px, ' +
+                                       '        50px 100px, 0px 50px)' },
+                           { clipPath: 'inset(20px round 10px)' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  const frames = anim.effect.getKeyframes();
+  const slots = frames.length - 1;
+  for (var i = 0; i < frames.length; ++i) {
+    assert_equals(frames[i].computedOffset, i / slots,
+                  'computedOffset of frame ' + i);
+  }
+}, 'Test falling back to distribute spacing when using different basic shapes');
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { clipPath: 'circle(10px)' },
+                           { clipPath: 'circle(20px) content-box' },
+                           { clipPath: 'circle(70px)' },
+                           { clipPath: 'circle(10px) padding-box' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  const frames = anim.effect.getKeyframes();
+  const slots = frames.length - 1;
+  for (var i = 0; i < frames.length; ++i) {
+    assert_equals(frames[i].computedOffset, i / slots,
+                  'computedOffset of frame ' + i);
+  }
+}, 'Test falling back to distribute spacing when using different ' +
+   'reference boxes');
+
+test(function(t) {
+  // 1st: circle(calc(20px) at  calc(20px + 0%)  calc(10px + 0%))
+  // 2nd: circle(calc(50px) at  calc(10px + 0%)  calc(10px + 0%))
+  // 3rd: circle(70px at  calc(10px + 0%)  calc(50px + 0%))
+  // 4th: circle(10px at  calc(50px + 0%)  calc(30px + 0%))
+  var anim =
+    createDiv(t).animate([ { clipPath: 'circle(calc(10px + 10px) ' +
+                                       '       at 20px 10px)' },
+                           { clipPath: 'circle(calc(20px + 30px) ' +
+                                       '       at 10px 10px)' },
+                           { clipPath: 'circle(70px at 10px 50px)' },
+                           { clipPath: 'circle(10px at 50px 30px)' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  var dist = [ 0,
+               Math.sqrt(30 * 30 + 10 * 10),
+               Math.sqrt(20 * 20 + 40 * 40),
+               Math.sqrt(60 * 60 + 40 * 40 + 20 * 20) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on circle' );
+
+test(function(t) {
+  // 1st: ellipse(20px calc(20px) at  calc(0px + 50%)  calc(0px + 50%))
+  // 2nd: ellipse(20px calc(50px) at  calc(0px + 50%)  calc(0px + 50%))
+  // 3rd: ellipse(30px 70px at  calc(0px + 50%)  calc(0px + 50%))
+  // 4th: ellipse(30px 10px at  calc(0px + 50%)  calc(0px + 50%))
+  var anim =
+    createDiv(t).animate([ { clipPath: 'ellipse(20px calc(10px + 10px))' },
+                           { clipPath: 'ellipse(20px calc(20px + 30px))' },
+                           { clipPath: 'ellipse(30px 70px)' },
+                           { clipPath: 'ellipse(30px 10px)' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  var dist = [ 0,
+               Math.sqrt(30 * 30),
+               Math.sqrt(10 * 10 + 20 * 20),
+               Math.sqrt(60 * 60) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on ellipse' );
+
+test(function(t) {
+  // 1st: polygon(nonzero, 50px 0px, 100px 50px, 50px 100px, 0px 50px)
+  // 2nd: polygon(nonzero, 40px 0px, 100px 70px, 10px 100px, 0px 70px)
+  // 3rd: polygon(nonzero, 100px 0px, 100px 100px, 10px 80px, 0px 50px)
+  // 4th: polygon(nonzero, 100px 100px, -10px 100px, 20px 80px, 20px 50px)
+  var anim =
+    createDiv(t).animate([ { clipPath: 'polygon(50px 0px, 100px 50px, ' +
+                                       '        50px 100px, 0px 50px)' },
+                           { clipPath: 'polygon(40px 0px, 100px 70px, ' +
+                                       '        10px 100px, 0px 70px)' },
+                           { clipPath: 'polygon(100px 0px, 100px 100px, ' +
+                                       '        10px 80px, 0px 50px)' },
+                           { clipPath: 'polygon(100px 100px, -10px 100px, ' +
+                                       '        20px 80px, 20px 50px)' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  var dist = [ 0,
+               Math.sqrt(10 * 10 + 20 * 20 + 40 * 40 + 20 * 20),
+               Math.sqrt(60 * 60 + 30 * 30 + 20 * 20 + 20 * 20),
+               Math.sqrt(100 * 100 + 110 * 110 + 10 * 10 + 20 * 20) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on polygon' );
+
+test(function(t) {
+  // Note: Rounding corners are 4 CSS pair values and
+  //       each pair has x & y components.
+  // 1st: inset(5px 5px 5px 5px round 40px 30px 20px 5px / 40px 30px 20px 5px)
+  // 2nd: inset(10px 5px 10px 5px round 50px 60px / 50px 60px)
+  // 3rd: inset(40px 40px 40px 40px round 10px / 10px)
+  // 4th: inset(30px 40px 30px 40px round 20px / 20px)
+  var anim =
+    createDiv(t).animate([ { clipPath: 'inset(5px 5px 5px 5px ' +
+                                       '      round 40px 30px 20px 5px)' },
+                           { clipPath: 'inset(10px 5px round 50px 60px)' },
+                           { clipPath: 'inset(40px 40px round 10px)' },
+                           { clipPath: 'inset(30px 40px round 20px)' } ],
+                         { spacing: 'paced(clip-path)' });
+
+  var dist = [ 0,
+               Math.sqrt(5 * 5 * 2 + (50 - 40) * (50 - 40) * 2 +
+                                     (60 - 30) * (60 - 30) * 2 +
+                                     (50 - 20) * (50 - 20) * 2 +
+                                     (60 - 5) * (60 - 5) * 2),
+               Math.sqrt(30 * 30 * 2 + 35 * 35 * 2 + (50 - 10) * (50 - 10) * 4 +
+                                                     (60 - 10) * (60 - 10) * 4),
+               Math.sqrt(10 * 10 * 2 + (20 - 10) * (20 - 10) * 8) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on inset' );
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-transform.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-transform.html
new file mode 100644
index 0000000..189a25e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/spacing-keyframes-transform.html
@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Keyframe spacing tests on transform</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#spacing-keyframes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+const pi  = Math.PI;
+const cos = Math.cos;
+const sin = Math.sin;
+const tan = Math.tan;
+const sqrt = Math.sqrt;
+
+// Help function for testing the computed offsets by the distance array.
+function assert_animation_offsets(anim, dist) {
+  const epsilon = 0.00000001;
+  const frames = anim.effect.getKeyframes();
+  const cumDist = dist.reduce( (prev, curr) => {
+    prev.push(prev.length == 0 ? curr : curr + prev[prev.length - 1]);
+    return prev;
+  }, []);
+
+  const total = cumDist[cumDist.length - 1];
+  for (var i = 0; i < frames.length; ++i) {
+    assert_approx_equals(frames[i].computedOffset, cumDist[i] / total,
+                         epsilon, 'computedOffset of frame ' + i);
+  }
+}
+
+function getAngleDist(rotate1, rotate2) {
+  function quaternion(axis, angle) {
+    var x = axis[0] * sin(angle/2.0);
+    var y = axis[1] * sin(angle/2.0);
+    var z = axis[2] * sin(angle/2.0);
+    var w = cos(angle/2.0);
+    return { 'x': x, 'y': y, 'z': z, 'w': w };
+  }
+  var q1 = quaternion(rotate1.axis, rotate1.angle);
+  var q2 = quaternion(rotate2.axis, rotate2.angle);
+  var dotProduct = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
+  return 2.0 * Math.acos(dotProduct);
+}
+
+function createMatrix(elements, Is3D) {
+  return (Is3D ? "matrix3d" : "matrix") + "(" + elements.join() + ")";
+}
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "none" },
+                                    { transform: "translate(-20px)" },
+                                    { transform: "translate(100px)" },
+                                    { transform: "translate(50px)"} ],
+                                  { spacing: "paced(transform)" });
+  assert_animation_offsets(anim, [ 0, 20, 120, 50 ]);
+}, 'Test spacing on translate' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { transform: "none" },
+                           { transform: "translate3d(-20px, 10px, 100px)" },
+                           { transform: "translate3d(100px, 200px, 50px)" },
+                           { transform: "translate(50px, -10px)"} ],
+                         { spacing: "paced(transform)" });
+  var dist = [ 0,
+               sqrt(20 * 20 + 10 * 10 + 100 * 100),
+               sqrt(120 * 120 + 190 * 190 + 50 * 50),
+               sqrt(50 * 50 + 210 * 210 + 50 * 50) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on translate3d' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "scale(0.5)" },
+                                    { transform: "scale(4.5)" },
+                                    { transform: "scale(2.5)" },
+                                    { transform: "none"} ],
+                                  { spacing: "paced(transform)" });
+  assert_animation_offsets(anim, [ 0, 4.0, 2.0, 1.5 ]);
+}, 'Test spacing on scale' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "scale(0.5, 0.5)" },
+                                    { transform: "scale3d(4.5, 5.0, 2.5)" },
+                                    { transform: "scale3d(2.5, 1.0, 2.0)" },
+                                    { transform: "scale3d(1, 0.5, 1.0)"} ],
+                                  { spacing:"paced(transform)" });
+  var dist = [ 0,
+               sqrt(4.0 * 4.0 + 4.5 * 4.5 + 1.5 * 1.5),
+               sqrt(2.0 * 2.0 + 4.0 * 4.0 + 0.5 * 0.5),
+               sqrt(1.5 * 1.5 + 0.5 * 0.5 + 1.0 * 1.0) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on scale3d' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "rotate(60deg)" },
+                                    { transform: "none" },
+                                    { transform: "rotate(720deg)" },
+                                    { transform: "rotate(-360deg)"} ],
+                                  { spacing: "paced(transform)" });
+  assert_animation_offsets(anim, [ 0, 60, 720, 1080 ]);
+}, 'Test spacing on rotate' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "rotate3d(1,0,0,60deg)" },
+                                    { transform: "rotate3d(1,0,0,70deg)" },
+                                    { transform: "rotate3d(0,0,1,-110deg)" },
+                                    { transform: "rotate3d(1,0,0,219deg)"} ],
+                                  { spacing: "paced(transform)" });
+  var dist = [ 0,
+               getAngleDist({ axis: [1,0,0], angle: 60 * pi / 180 },
+                            { axis: [1,0,0], angle: 70 * pi / 180 }),
+               getAngleDist({ axis: [0,1,0], angle: 70 * pi / 180 },
+                            { axis: [0,0,1], angle: -110 * pi / 180 }),
+               getAngleDist({ axis: [0,0,1], angle: -110 * pi / 180 },
+                            { axis: [1,0,0], angle: 219 * pi / 180 }) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on rotate3d' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "skew(60deg)" },
+                                    { transform: "none" },
+                                    { transform: "skew(-90deg)" },
+                                    { transform: "skew(90deg)"} ],
+                                  { spacing: "paced(transform)" });
+  assert_animation_offsets(anim, [ 0, 60, 90, 180 ]);
+}, 'Test spacing on skew' );
+
+test(function(t) {
+  var anim = createDiv(t).animate([ { transform: "skew(60deg, 30deg)" },
+                                    { transform: "none" },
+                                    { transform: "skew(-90deg, 60deg)" },
+                                    { transform: "skew(90deg, 60deg)"} ],
+                                  { spacing: "paced(transform)" });
+  var dist = [ 0,
+               sqrt(60 * 60 + 30 * 30),
+               sqrt(90 * 90 + 60 * 60),
+               sqrt(180 * 180 + 0) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on skew along both X and Y' );
+
+test(function(t) {
+  // We calculate the distance of two perspective functions by converting them
+  // into two matrix3ds, and then do matrix decomposition to get two
+  // perspective vectors, so the equivalent perspective vectors are:
+  // perspective 1: (0, 0, -1/128, 1);
+  // perspective 2: (0, 0, -1/infinity = 0, 1);
+  // perspective 3: (0, 0, -1/1024, 1);
+  // perspective 4: (0, 0, -1/32, 1);
+  var anim = createDiv(t).animate([ { transform: "perspective(128px)" },
+                                    { transform: "none" },
+                                    { transform: "perspective(1024px)" },
+                                    { transform: "perspective(32px)"} ],
+                                  { spacing: "paced(transform)" });
+  assert_animation_offsets(anim, [ 0, 1/128, 1/1024, 1/32 - 1/1024 ]);
+}, 'Test spacing on perspective' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { transform: "none" },
+                           { transform: "rotate(180deg) translate(0px)" },
+                           { transform: "rotate(180deg) translate(1000px)" },
+                           { transform: "rotate(360deg) translate(1000px)"} ],
+                         { spacing: "paced(transform)" });
+  var dist = [ 0,
+               sqrt(pi * pi + 0),
+               sqrt(1000 * 1000),
+               sqrt(pi * pi + 0) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on matched transform lists' );
+
+test(function(t) {
+  // matrix1 => translate(100px, 50px), skewX(60deg).
+  // matrix2 => translate(1000px), rotate(180deg).
+  // matrix3 => translate(1000px), scale(1.5, 0.7).
+  const matrix1 = createMatrix([ 1, 0, tan(pi/4.0), 1, 100, 50 ]);
+  const matrix2 = createMatrix([ cos(pi), sin(pi),
+                                -sin(pi), cos(pi),
+                                 1000, 0 ]);
+  const matrix3 = createMatrix([ 1.5, 0, 0, 0.7, 1000, 0 ]);
+  var anim = createDiv(t).animate([ { transform: "none" },
+                                    { transform: matrix1 },
+                                    { transform: matrix2 },
+                                    { transform: matrix3 } ],
+                                  { spacing: "paced(transform)" });
+  var dist = [ 0,
+               sqrt(100 * 100 + 50 * 50 + pi/4 * pi/4),
+               sqrt(900 * 900 + 50 * 50 + pi * pi + pi/4 * pi/4),
+               sqrt(pi * pi + 0.5 * 0.5 + 0.3 * 0.3) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on matrix' );
+
+test(function(t) {
+  // matrix1 => translate3d(100px, 50px, -10px), skew(60deg).
+  // matrix2 => translate3d(1000px, 0, 0), rotate3d(1, 0, 0, 180deg).
+  // matrix3 => translate3d(1000px, 0, 0), scale3d(1.5, 0.7, 2.2).
+  const matrix1 = createMatrix([ 1, 0, 0, 0,
+                                 tan(pi/4.0), 1, 0, 0,
+                                 0, 0, 1, 0,
+                                 100, 50, -10, 1 ], true);
+  const matrix2 = createMatrix([ 1, 0, 0, 0,
+                                 0, cos(pi), sin(pi), 0,
+                                 0, -sin(pi), cos(pi), 0,
+                                 1000, 0, 0, 1 ], true);
+  const matrix3 = createMatrix([ 1.5, 0, 0, 0,
+                                 0, 0.7, 0, 0,
+                                 0, 0, 2.2, 0,
+                                 1000, 0, 0, 1 ], true);
+  var anim = createDiv(t).animate([ { transform: "none" },
+                                    { transform: matrix1 },
+                                    { transform: matrix2 },
+                                    { transform: matrix3 } ],
+                                  { spacing: "paced(transform)" });
+  var dist = [ 0,
+               sqrt(100 * 100 + 50 * 50 + 10 * 10 + pi/4 * pi/4),
+               sqrt(900 * 900 + 50 * 50 + 10 * 10 + pi/4 * pi/4 + pi * pi),
+               sqrt(0.5 * 0.5 + 0.3 * 0.3 + 1.2 * 1.2 + pi * pi) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on matrix3d' );
+
+test(function(t) {
+  var anim =
+    createDiv(t).animate([ { transform: "none" },
+                           { transform: "translate(100px, 50px) skew(45deg)" },
+                           { transform: "translate(1000px) " +
+                                        "rotate3d(1, 0, 0, 180deg)" },
+                           { transform: "translate(1000px) " +
+                                        "scale3d(2.5, 0.5, 0.7)" } ],
+                         { spacing: "paced(transform)" });
+
+  var dist = [ 0,
+               sqrt(100 * 100 + 50 * 50 + pi/4 * pi/4),
+               sqrt(900 * 900 + 50 * 50 + pi/4 * pi/4 + pi * pi),
+               sqrt(1.5 * 1.5 + 0.5 * 0.5 + 0.3 * 0.3 + pi * pi) ];
+  assert_animation_offsets(anim, dist);
+}, 'Test spacing on mismatched transform list' );
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/type-per-property.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/type-per-property.html
deleted file mode 100644
index 683b0093..0000000
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/animation-types/type-per-property.html
+++ /dev/null
@@ -1,569 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Tests for animation types</title>
-<link rel="help" href="https://w3c.github.io/web-animations/#animation-types">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../../testcommon.js"></script>
-<style>
-html {
-  font-size: 10px;
-}
-</style>
-<body>
-<div id="log"></div>
-<script>
-"use strict";
-
-var gCSSProperties = {
-  "align-content": {
-    // https://drafts.csswg.org/css-align/#propdef-align-content
-    tests: [
-      discrete("flex-start", "flex-end")
-    ]
-  },
-  "align-items": {
-    // https://drafts.csswg.org/css-align/#propdef-align-items
-    tests: [
-      discrete("flex-start", "flex-end")
-    ]
-  },
-  "align-self": {
-    // https://drafts.csswg.org/css-align/#propdef-align-self
-    tests: [
-      discrete("flex-start", "flex-end")
-    ]
-  },
-  "clip-rule": {
-    // https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule
-    tests: [
-      discrete("evenodd", "nonzero")
-    ]
-  },
-  "color-interpolation": {
-    // https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty
-    tests: [
-      discrete("linearRGB", "auto")
-    ]
-  },
-  "color-interpolation-filters": {
-    // https://drafts.fxtf.org/filters-1/#propdef-color-interpolation-filters
-    tests: [
-      discrete("sRGB", "linearRGB")
-    ]
-  },
-  "dominant-baseline": {
-    // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline
-    tests: [
-      discrete("ideographic", "alphabetic")
-    ]
-  },
-  "fill-rule": {
-    // https://svgwg.org/svg2-draft/painting.html#FillRuleProperty
-    tests: [
-      discrete("evenodd", "nonzero")
-    ]
-  },
-  "flex-basis": {
-    // https://drafts.csswg.org/css-flexbox/#propdef-flex-basis
-    tests: [
-      lengthPercentageOrCalc(),
-      discrete("auto", "10px")
-    ]
-  },
-  "flex-direction": {
-    // https://drafts.csswg.org/css-flexbox/#propdef-flex-direction
-    tests: [
-      discrete("row", "row-reverse")
-    ]
-  },
-  "flex-grow": {
-    // https://drafts.csswg.org/css-flexbox/#flex-grow-property
-    tests: [
-      positiveNumber()
-    ]
-  },
-  "flex-shrink": {
-    // https://drafts.csswg.org/css-flexbox/#propdef-flex-shrink
-    tests: [
-      positiveNumber()
-    ]
-  },
-  "flex-wrap": {
-    // https://drafts.csswg.org/css-flexbox/#propdef-flex-wrap
-    tests: [
-      discrete("nowrap", "wrap")
-    ]
-  },
-  "font-style": {
-    // https://drafts.csswg.org/css-fonts/#propdef-font-style
-    tests: [
-      discrete("italic", "oblique")
-    ]
-  },
-  "image-rendering": {
-    // https://drafts.csswg.org/css-images/#propdef-image-rendering
-    tests: [
-      discrete("optimizeQuality", "pixelated")
-    ]
-  },
-  "justify-content": {
-    // https://drafts.csswg.org/css-align/#propdef-justify-content
-    tests: [
-      discrete("baseline", "last-baseline")
-    ]
-  },
-  "justify-items": {
-    // https://drafts.csswg.org/css-align/#propdef-justify-items
-    tests: [
-      discrete("baseline", "last-baseline")
-    ]
-  },
-  "justify-self": {
-    // https://drafts.csswg.org/css-align/#propdef-justify-self
-    tests: [
-      discrete("baseline", "last-baseline")
-    ]
-  },
-  "mask-type": {
-    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-type
-    tests: [
-      discrete("alpha", "luminance")
-    ]
-  },
-  "order": {
-    // https://drafts.csswg.org/css-flexbox/#propdef-order
-    tests: [
-      integer()
-    ]
-  },
-  "pointer-events": {
-    // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
-    tests: [
-      discrete("fill", "none")
-    ]
-  },
-  "ruby-align": {
-    // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align
-    tests: [
-      discrete("start", "center")
-    ]
-  },
-  "ruby-position": {
-    // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-position
-    tests: [
-      discrete("under", "over")
-    ],
-    tagName: "ruby"
-  },
-  "shape-rendering": {
-    // https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty
-    tests: [
-      discrete("optimizeSpeed", "crispEdges")
-    ]
-  },
-  "stroke-linecap": {
-    // https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty
-    tests: [
-      discrete("round", "square")
-    ]
-  },
-  "stroke-linejoin": {
-    // https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty
-    tests: [
-      discrete("round", "miter")
-    ],
-    tagName: "rect"
-  },
-  "text-anchor": {
-    // https://svgwg.org/svg2-draft/text.html#TextAnchorProperty
-    tests: [
-      discrete("middle", "end")
-    ]
-  },
-  "text-combine-upright": {
-    // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright
-    tests: [
-      discrete("all", "digits")
-    ]
-  },
-  "text-decoration-line": {
-    // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-line
-    tests: [
-      discrete("underline", "overline")
-    ]
-  },
-  "text-orientation": {
-    // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation
-    tests: [
-      discrete("upright", "sideways")
-    ]
-  },
-  "text-rendering": {
-    // https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
-    tests: [
-      discrete("optimizeSpeed", "optimizeLegibility")
-    ]
-  },
-  "vector-effect": {
-    // https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty
-    tests: [
-      discrete("none", "non-scaling-stroke")
-    ]
-  },
-  "visibility": {
-    // https://drafts.csswg.org/css2/visufx.html#propdef-visibility
-    tests: [
-      visibility()
-    ]
-  },
-  "word-break": {
-    // https://drafts.csswg.org/css-text-3/#propdef-word-break
-    tests: [
-      discrete("keep-all", "break-all")
-    ]
-  },
-  "writing-mode": {
-    // https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode
-    tests: [
-      discrete("vertical-rl", "sideways-rl")
-    ]
-  },
-}
-
-for (var property in gCSSProperties) {
-  if (!isSupported(property)) {
-    continue;
-  }
-  var testData = gCSSProperties[property];
-  testData.tests.forEach(function(testFunction) {
-    testFunction(property, testData);
-  });
-}
-
-function discrete(from, to) {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = [from, to];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: from.toLowerCase() },
-                            { time: 499,  expected: from.toLowerCase() },
-                            { time: 500,  expected: to.toLowerCase() },
-                            { time: 1000, expected: to.toLowerCase() }]);
-    }, property + " uses discrete animation when animating between '"
-       + from + "' and '" + to + "' with linear easing");
-
-    test(function(t) {
-      // Easing: http://cubic-bezier.com/#.68,0,1,.01
-      // With this curve, we don't reach the 50% point until about 95% of
-      // the time has expired.
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = [from, to];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both",
-                                       easing: "cubic-bezier(0.68,0,1,0.01)" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: from.toLowerCase() },
-                            { time: 940,  expected: from.toLowerCase() },
-                            { time: 960,  expected: to.toLowerCase() }]);
-    }, property + " uses discrete animation when animating between '"
-       + from + "' and '" + to + "' with effect easing");
-
-    test(function(t) {
-      // Easing: http://cubic-bezier.com/#.68,0,1,.01
-      // With this curve, we don't reach the 50% point until about 95% of
-      // the time has expired.
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = [from, to];
-      keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: from.toLowerCase() },
-                            { time: 940,  expected: from.toLowerCase() },
-                            { time: 960,  expected: to.toLowerCase() }]);
-    }, property + " uses discrete animation when animating between '"
-       + from + "' and '" + to + "' with keyframe easing");
-  }
-}
-
-function length() {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["10px", "50px"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10px" },
-                            { time: 500,  expected: "30px" },
-                            { time: 1000, expected: "50px" }]);
-    }, property + " supports animating as a length");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["1rem", "5rem"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10px" },
-                            { time: 500,  expected: "30px" },
-                            { time: 1000, expected: "50px" }]);
-    }, property + " supports animating as a length of rem");
-  }
-}
-
-function percentage() {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["10%", "50%"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10%" },
-                            { time: 500,  expected: "30%" },
-                            { time: 1000, expected: "50%" }]);
-    }, property + " supports animating as a percentage");
-  }
-}
-
-function integer() {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = [-2, 2];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "-2" },
-                            { time: 500,  expected: "0" },
-                            { time: 1000, expected: "2" }]);
-    }, property + " supports animating as an integer");
-  }
-}
-
-function positiveNumber() {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = [1.1, 1.5];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "1.1" },
-                            { time: 500,  expected: "1.3" },
-                            { time: 1000, expected: "1.5" }]);
-    }, property + " supports animating as a positive number");
-  }
-}
-
-function lengthPercentageOrCalc() {
-  return function(property, options) {
-    length()(property, options);
-    percentage()(property, options);
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["10px", "20%"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10px" },
-                            { time: 500,  expected: "calc(5px + 10%)" },
-                            { time: 1000, expected: "20%" }]);
-    }, property + " supports animating as combination units 'px' and '%'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["10%", "2em"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10%" },
-                            { time: 500,  expected: "calc(10px + 5%)" },
-                            { time: 1000, expected: "20px" }]);
-    }, property + " supports animating as combination units '%' and 'em'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["1em", "2rem"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10px" },
-                            { time: 500,  expected: "15px" },
-                            { time: 1000, expected: "20px" }]);
-    }, property + " supports animating as combination units 'em' and 'rem'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["10px", "calc(1em + 20%)"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "10px" },
-                            { time: 500,  expected: "calc(10px + 10%)" },
-                            { time: 1000, expected: "calc(10px + 20%)" }]);
-    }, property + " supports animating as combination units 'px' and 'calc'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["calc(10px + 10%)", "calc(1em + 1rem + 20%)"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,
-                              expected: "calc(10px + 10%)" },
-                            { time: 500,
-                              expected: "calc(15px + 15%)" },
-                            { time: 1000,
-                              expected: "calc(20px + 20%)" }]);
-    }, property + " supports animating as a calc");
-  }
-}
-
-function visibility() {
-  return function(property, options) {
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["visible", "hidden"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "visible" },
-                            { time: 999,  expected: "visible" },
-                            { time: 1000, expected: "hidden" }]);
-    }, property + " uses visibility animation when animating "
-       + "from 'visible' to 'hidden'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["hidden", "visible"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "hidden" },
-                            { time: 1,    expected: "visible" },
-                            { time: 1000, expected: "visible" }]);
-    }, property + " uses visibility animation when animating "
-     + "from 'hidden' to 'visible'");
-
-    test(function(t) {
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["hidden", "collapse"];
-      var target = createElement(t, options.tagName);
-      var animation = target.animate(keyframes,
-                                     { duration: 1000, fill: "both" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "hidden" },
-                            { time: 499,  expected: "hidden" },
-                            { time: 500,  expected: "collapse" },
-                            { time: 1000, expected: "collapse" }]);
-    }, property + " uses visibility animation when animating "
-     + "from 'hidden' to 'collapse'");
-
-    test(function(t) {
-      // Easing: http://cubic-bezier.com/#.68,-.55,.26,1.55
-      // With this curve, the value is less than 0 till about 34%
-      // also more than 1 since about 63%
-      var idlName = propertyToIDL(property);
-      var keyframes = {};
-      keyframes[idlName] = ["visible", "hidden"];
-      var target = createElement(t, options.tagName);
-      var animation =
-        target.animate(keyframes,
-                       { duration: 1000, fill: "both",
-                         easing: "cubic-bezier(0.68, -0.55, 0.26, 1.55)" });
-      testAnimationSamples(animation, idlName,
-                           [{ time: 0,    expected: "visible" },
-                            { time: 1,    expected: "visible" },
-                            { time: 330,  expected: "visible" },
-                            { time: 340,  expected: "visible" },
-                            { time: 620,  expected: "visible" },
-                            { time: 630,  expected: "hidden" },
-                            { time: 1000, expected: "hidden" }]);
-    }, property + " uses visibility animation when animating "
-     + "from 'visible' to 'hidden' with easeInOutBack easing");
-  }
-}
-
-function testAnimationSamples(animation, idlName, testSamples) {
-  var target = animation.effect.target;
-  testSamples.forEach(function(testSample) {
-    animation.currentTime = testSample.time;
-    assert_equals(getComputedStyle(target)[idlName], testSample.expected,
-                  "The value should be " + testSample.expected +
-                  " at " + testSample.time + "ms");
-  });
-}
-
-function isSupported(property) {
-  var testKeyframe = new TestKeyframe(propertyToIDL(property));
-  try {
-    // Since TestKeyframe returns 'undefined' for |property|,
-    // the KeyframeEffect constructor will throw
-    // if the string "undefined" is not a valid value for the property.
-    new KeyframeEffect(null, testKeyframe);
-  } catch(e) {}
-  return testKeyframe.propAccessCount !== 0;
-}
-
-function TestKeyframe(testProp) {
-  var _propAccessCount = 0;
-
-  Object.defineProperty(this, testProp, {
-    get: function() { _propAccessCount++; },
-    enumerable: true
-  });
-
-  Object.defineProperty(this, 'propAccessCount', {
-    get: function() { return _propAccessCount; }
-  });
-}
-
-function propertyToIDL(property) {
-  // https://w3c.github.io/web-animations/#animation-property-name-to-idl-attribute-name
-  if (property === "float") {
-    return "cssFloat";
-  }
-  return property.replace(/-[a-z]/gi,
-                          function (str) {
-                            return str.substr(1).toUpperCase(); });
-}
-
-</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/combining-effects/effect-composition.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/combining-effects/effect-composition.html
new file mode 100644
index 0000000..7604bd21
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/animation-model/combining-effects/effect-composition.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for effect composition</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#effect-composition">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="../../testcommon.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+[ 'accumulate', 'add' ].forEach(function(composite) {
+  test(function(t) {
+    var div = createDiv(t);
+    div.style.marginLeft = '10px';
+    var anim =
+      div.animate({ marginLeft: ['0px', '10px'], composite }, 100);
+
+    anim.currentTime = 50;
+    assert_equals(getComputedStyle(div).marginLeft, '15px',
+      'Animated margin-left style at 50%');
+  }, composite + ' onto the base value');
+
+  test(function(t) {
+    var div = createDiv(t);
+    var anims = [];
+    anims.push(div.animate({ marginLeft: ['10px', '20px'],
+                             composite: 'replace' },
+                           100));
+    anims.push(div.animate({ marginLeft: ['0px', '10px'],
+                             composite },
+                           100));
+
+    anims.forEach(function(anim) {
+      anim.currentTime = 50;
+    });
+
+    assert_equals(getComputedStyle(div).marginLeft, '20px',
+      'Animated style at 50%');
+  }, composite + ' onto an underlying animation value');
+
+  test(function(t) {
+    var div = createDiv(t);
+    div.style.marginLeft = '10px';
+    var anim =
+      div.animate([{ marginLeft: '10px', composite },
+                   { marginLeft: '30px', composite: 'replace' }],
+                  100);
+
+    anim.currentTime = 50;
+    assert_equals(getComputedStyle(div).marginLeft, '25px',
+      'Animated style at 50%');
+  }, 'Composite when mixing ' + composite + ' and replace');
+
+  test(function(t) {
+    var div = createDiv(t);
+    div.style.marginLeft = '10px';
+    var anim =
+      div.animate([{ marginLeft: '10px', composite: 'replace' },
+                   { marginLeft: '20px' }],
+                  { duration: 100 , composite });
+
+    anim.currentTime = 50;
+    assert_equals(getComputedStyle(div).marginLeft, '20px',
+      'Animated style at 50%');
+  }, composite + ' specified on a keyframe overrides the composite mode of ' +
+     'the effect');
+
+  test(function(t) {
+    var div = createDiv(t);
+    div.style.marginLeft = '10px';
+    var anim =
+      div.animate([{ marginLeft: '10px', composite: 'replace' },
+                   { marginLeft: '20px' }],
+                  100);
+
+    anim.effect.composite = composite;
+    anim.currentTime = 50;                       // (10 + (10 + 20)) * 0.5
+    assert_equals(getComputedStyle(div).marginLeft, '20px',
+      'Animated style at 50%');
+  }, 'unspecified composite mode on a keyframe is overriden by setting ' +
+     composite + ' of the effect');
+});
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/AnimationEffectTiming/endDelay.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/AnimationEffectTiming/endDelay.html
index 3f27daa..40136b4 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/AnimationEffectTiming/endDelay.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/AnimationEffectTiming/endDelay.html
@@ -66,30 +66,17 @@
   var div = createDiv(t);
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 100000, endDelay: 30000 });
-  var finishedTimelineTime;
-  anim.finished.then(function() {
-    finishedTimelineTime = anim.timeline.currentTime;
-  });
-
-  var receivedEvents = [];
-  anim.onfinish = function(event) {
-    receivedEvents.push(event);
-  }
-
   anim.ready.then(function() {
     anim.currentTime = 110000; // during endDelay
+    anim.onfinish = t.step_func(function(event) {
+      assert_unreached('onfinish event should not be fired during endDelay');
+    });
     return waitForAnimationFrames(2);
   }).then(t.step_func(function() {
-    assert_equals(receivedEvents.length, 0,
-      'onfinish event is should not be fired' +
-      'when currentTime is during endDelay');
+    anim.onfinish = t.step_func(function(event) {
+      t.done();
+    });
     anim.currentTime = 130000; // after endTime
-    return waitForAnimationFrames(2);
-  })).then(t.step_func_done(function() {
-    assert_equals(receivedEvents.length, 1, 'length of array should be one');
-    assert_equals(receivedEvents[0].timelineTime, finishedTimelineTime,
-      'receivedEvents[0].timelineTime should equal to the animation timeline '
-      + 'when finished promise is resolved');
   }));
 }, 'onfinish event is fired currentTime is after endTime');
 
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/composite.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/composite.html
new file mode 100644
index 0000000..1825b71
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/composite.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.composite tests</title>
+<link rel="help"
+      href="https://w3c.github.io/web-animations/#dom-keyframeeffect-composite">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(function(t) {
+  var anim = createDiv(t).animate(null);
+  assert_equals(anim.effect.composite, 'replace',
+                'The default value should be replace');
+}, 'Default value');
+
+test(function(t) {
+  var anim = createDiv(t).animate(null);
+  anim.effect.composite = 'add';
+  assert_equals(anim.effect.composite, 'add',
+                'The effect composite value should be replaced');
+}, 'Change composite value');
+
+test(function(t) {
+  var anim = createDiv(t).animate({ left: '10px' });
+
+  anim.effect.composite = 'add';
+  var keyframes = anim.effect.getKeyframes();
+  assert_equals(keyframes[0].composite, undefined,
+                'unspecified keyframe composite value should be absent even ' +
+                'if effect composite is set');
+}, 'Unspecified keyframe composite value when setting effect composite');
+
+test(function(t) {
+  var anim = createDiv(t).animate({ left: '10px', composite: 'replace' });
+
+  anim.effect.composite = 'add';
+  var keyframes = anim.effect.getKeyframes();
+  assert_equals(keyframes[0].composite, 'replace',
+                'specified keyframe composite value should not be overridden ' +
+                'by setting effect composite');
+}, 'Specified keyframe composite value when setting effect composite');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/constructor.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/constructor.html
index 97c7613..f7c61995 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/constructor.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/constructor.html
@@ -110,7 +110,7 @@
     var effect = new KeyframeEffectReadOnly(target, {
       left: ["10px", "20px"]
     }, { composite: composite });
-    assert_equals(effect.getKeyframes()[0].composite, composite,
+    assert_equals(effect.getKeyframes()[0].composite, undefined,
                   "resulting composite for '" + composite + "'");
   });
   gBadCompositeValueTests.forEach(function(composite) {
@@ -120,8 +120,8 @@
       }, { composite: composite });
     });
   });
-}, "composite values are parsed correctly when passed to the " +
-   "KeyframeEffectReadOnly constructor in KeyframeTimingOptions");
+}, "composite value is absent if the composite operation specified on the " +
+   "keyframe effect is being used");
 
 gPropertyIndexedKeyframesTests.forEach(function(subtest) {
   test(function(t) {
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/copy-contructor.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/copy-contructor.html
new file mode 100644
index 0000000..bc27838
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/copy-contructor.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>KeyframeEffect copy constructor tests</title>
+<link rel="help"
+href="https://w3c.github.io/web-animations/#dom-keyframeeffect-keyframeeffect-source">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(function(t) {
+  var effect = new KeyframeEffectReadOnly(createDiv(t), null);
+  assert_equals(effect.constructor.name, 'KeyframeEffectReadOnly');
+  assert_equals(effect.timing.constructor.name,
+                'AnimationEffectTimingReadOnly');
+
+  // Make a mutable copy
+  var copiedEffect = new KeyframeEffect(effect);
+  assert_equals(copiedEffect.constructor.name, 'KeyframeEffect');
+  assert_equals(copiedEffect.timing.constructor.name, 'AnimationEffectTiming');
+}, 'Test mutable copy from a KeyframeEffectReadOnly source');
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/iterationComposite.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/iterationComposite.html
index 743d108..eb5c032 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/iterationComposite.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffect/iterationComposite.html
@@ -12,6 +12,26 @@
 test(function(t) {
   var div = createDiv(t);
   var anim =
+    div.animate({ alignContent: ['flex-start', 'flex-end'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).alignContent, 'flex-end',
+    'Animated align-content style at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).alignContent, 'flex-start',
+    'Animated align-content style at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).alignContent, 'flex-end',
+    'Animated align-content style at 50s of the third iteration');
+}, 'iterationComposite of discrete type animation (align-content)');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
     div.animate({ marginLeft: ['0px', '10px'] },
                 { duration: 100 * MS_PER_SEC,
                   easing: 'linear',
@@ -541,9 +561,35 @@
 
 test(function(t) {
   var div = createDiv(t);
-  // The transform list whose order is mismatched is compounded,
-  // so below animation is the same as;
-  // from matrix(2, 0, 0, 2, 0, 0) to matrix(3, 0, 0, 3, 30, 0)
+  var anim =
+    div.animate({ transform: ['matrix(2, 0, 0, 2, 0, 0)',
+                              'matrix(3, 0, 0, 3, 30, 0)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(2.5, 0, 0, 2.5, 15, 0)',
+    'Animated transform of matrix function at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    // scale(2) + (scale(3-1)*2) + translateX(30px)*2
+    'matrix(6, 0, 0, 6, 60, 0)',
+    'Animated transform of matrix function at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    // from: matrix(6, 0, 0, 6, 60, 0)
+    // to:   matrix(7, 0, 0, 7, 90, 0)
+    //         = scale(3) + (scale(3-1)*2) + translateX(30px)*3
+    'matrix(6.5, 0, 0, 6.5, 75, 0)',
+    'Animated transform of matrix function at 50s of the third iteration');
+}, 'iterationComposite of transform of matrix function');
+
+test(function(t) {
+  var div = createDiv(t);
   var anim =
     div.animate({ transform: ['translateX(0px) scale(2)',
                               'scale(3) translateX(10px)'] },
@@ -555,25 +601,29 @@
 
   anim.currentTime = anim.effect.timing.duration / 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(2.5, 0, 0, 2.5, 15, 0)', // scale(2.5) (0px + 30px*2) / 2
+    // Interpolate between matrix(2, 0, 0, 2,  0, 0) = translateX(0px) scale(2)
+    //                 and matrix(3, 0, 0, 3, 30, 0) = scale(3) translateX(10px)
+    'matrix(2.5, 0, 0, 2.5, 15, 0)',
     'Animated transform list at 50s of the first iteration');
   anim.currentTime = anim.effect.timing.duration * 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(4, 0, 0, 4, 60, 0)', // scale(2+(3-2)*2) (0px + 30px*2)
+    // 'from' and 'to' value are mismatched, so accumulate
+    // matrix(2, 0, 0, 2, 0, 0) onto matrix(3, 0, 0, 3, 30, 0) * 2
+    //  = scale(2) + (scale(3-1)*2) + translateX(30px)*2
+    'matrix(6, 0, 0, 6, 60, 0)',
     'Animated transform list at 0s of the third iteration');
   anim.currentTime += anim.effect.timing.duration / 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(5.5, 0, 0, 5.5, 135, 0)', // scale(4+7)/2 (60px + 210px)
+    // Interpolate between matrix(6, 0, 0, 6, 60, 0)
+    //                 and matrix(7, 0, 0, 7, 210, 0) = scale(7) translate(30px)
+    'matrix(6.5, 0, 0, 6.5, 135, 0)',
     'Animated transform list at 50s of the third iteration');
 }, 'iterationComposite of transform list animation whose order is mismatched');
 
 test(function(t) {
   var div = createDiv(t);
   // Even if each transform list does not have functions which exist in
-  // other pair of the list, we don't fill any missing functions at all,
-  // it's just computed as compounded matrices
-  // Below animation is the same as;
-  // from matrix(1, 0, 0, 1, 0, 0) to matrix(2, 0, 0, 2, 20, 0)
+  // other pair of the list, we don't fill any missing functions at all.
   var anim =
     div.animate({ transform: ['translateX(0px)',
                               'scale(2) translateX(10px)'] },
@@ -585,17 +635,110 @@
 
   anim.currentTime = anim.effect.timing.duration / 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(1.5, 0, 0, 1.5, 10, 0)', // scale(1.5) (0px + 10px*2) / 2
+    // Interpolate between matrix(1, 0, 0, 1,  0, 0) = translateX(0px)
+    //                 and matrix(2, 0, 0, 2, 20, 0) = scale(2) translateX(10px)
+    'matrix(1.5, 0, 0, 1.5, 10, 0)',
     'Animated transform list at 50s of the first iteration');
   anim.currentTime = anim.effect.timing.duration * 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(3, 0, 0, 3, 40, 0)', // scale(1+(2-1)*2) (0px + 20px*2)
+    // 'from' and 'to' value are mismatched, so accumulate
+    // matrix(1, 0, 0, 1, 0, 0) onto matrix(2, 0, 0, 2, 20, 0) * 2
+    //  = scale(1) + (scale(2-1)*2) + translateX(20px)*2
+    'matrix(3, 0, 0, 3, 40, 0)',
     'Animated transform list at 0s of the third iteration');
   anim.currentTime += anim.effect.timing.duration / 2;
   assert_equals(getComputedStyle(div).transform,
-    'matrix(3.5, 0, 0, 3.5, 80, 0)', // scale(3+4)/2 (40px + 20px)
+    // Interpolate between matrix(3, 0, 0, 3, 40, 0)
+    //                 and matrix(4, 0, 0, 4, 120, 0) =
+    //                       scale(2 + (2-1)*2) translate(10px * 3)
+    'matrix(3.5, 0, 0, 3.5, 80, 0)',
     'Animated transform list at 50s of the third iteration');
-}, 'iterationComposite of transform list animation whose order is mismatched');
+}, 'iterationComposite of transform list animation whose order is mismatched ' +
+   'because of missing functions');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['none',
+                              'translateX(10px)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 5, 0)', // (0px + 10px) / 2
+    'Animated transform list at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 0, 0)', // 'none' overrides any transforms.
+    'Animated transform list at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 15, 0)', // (0px + 10px*2)/2
+    'Animated transform list at 50s of the third iteration');
+}, 'iterationComposite of transform from none to translate');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['matrix3d(1, 0,  0, 0, ' +
+                                       '0, 1,  0, 0, ' +
+                                       '0, 0,  1, 0, ' +
+                                       '0, 0, 30, 1)',
+                              'matrix3d(1, 0,  0, 0, ' +
+                                       '0, 1,  0, 0, ' +
+                                       '0, 0,  1, 0, ' +
+                                       '0, 0, 50, 1)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 40, 1)',
+    'Animated transform of matrix3d function at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_equals(getComputedStyle(div).transform,
+    // translateZ(30px) + (translateZ(50px)*2)
+    'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)',
+    'Animated transform of matrix3d function at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_equals(getComputedStyle(div).transform,
+    // from: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)
+    // to:   matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 150, 1)
+    'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 140, 1)',
+    'Animated transform of matrix3d function at 50s of the third iteration');
+}, 'iterationComposite of transform of matrix3d function');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim =
+    div.animate({ transform: ['rotate3d(1, 1, 0, 0deg)',
+                              'rotate3d(1, 1, 0, 90deg)'] },
+                { duration: 100 * MS_PER_SEC,
+                  easing: 'linear',
+                  iterations: 10,
+                  iterationComposite: 'accumulate' });
+  anim.pause();
+
+  anim.currentTime = 0;
+  assert_equals(getComputedStyle(div).transform,
+    'matrix(1, 0, 0, 1, 0, 0)', // Actually not rotated at all.
+    'Animated transform of rotate3d function at 50s of the first iteration');
+  anim.currentTime = anim.effect.timing.duration * 2;
+  assert_matrix_equals(getComputedStyle(div).transform,
+    rotate3dToMatrix3d(1, 1, 0, Math.PI), // 180deg
+    'Animated transform of rotate3d function at 0s of the third iteration');
+  anim.currentTime += anim.effect.timing.duration / 2;
+  assert_matrix_equals(getComputedStyle(div).transform,
+    rotate3dToMatrix3d(1, 1, 0, 225 * Math.PI / 180), //((270 + 180) * 0.5)deg
+    'Animated transform of rotate3d function at 50s of the third iteration');
+}, 'iterationComposite of transform of rotate3d function');
 
 test(function(t) {
   var div = createDiv(t);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html
new file mode 100644
index 0000000..287ffe1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>KeyframeEffectReadOnly copy constructor tests</title>
+<link rel="help"
+href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-source">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(function(t) {
+  var effect = new KeyframeEffectReadOnly(createDiv(t), null);
+  var copiedEffect = new KeyframeEffectReadOnly(effect);
+  assert_equals(copiedEffect.target, effect.target, 'same target');
+}, 'Test copied keyframeEffectReadOnly has the same target');
+
+test(function(t) {
+  var effect =
+    new KeyframeEffectReadOnly(null,
+                               [ { marginLeft: '0px' },
+                                 { marginLeft: '-20px', easing: 'ease-in',
+                                   offset: 0.1 },
+                                 { marginLeft: '100px', easing: 'ease-out' },
+                                 { marginLeft: '50px' } ],
+                               { spacing: 'paced(margin-left)' });
+
+  var copiedEffect = new KeyframeEffectReadOnly(effect);
+  var KeyframesA = effect.getKeyframes();
+  var KeyframesB = copiedEffect.getKeyframes();
+  assert_equals(KeyframesA.length, KeyframesB.length, 'same keyframes length');
+
+  for (var i = 0; i < KeyframesA.length; ++i) {
+    assert_equals(KeyframesA[i].offset, KeyframesB[i].offset,
+                  'Keyframe ' + i + ' has the same offset');
+    assert_equals(KeyframesA[i].computedOffset, KeyframesB[i].computedOffset,
+                  'keyframe ' + i + ' has the same computedOffset');
+    assert_equals(KeyframesA[i].easing, KeyframesB[i].easing,
+                  'keyframe ' + i + ' has the same easing');
+    assert_equals(KeyframesA[i].composite, KeyframesB[i].composite,
+                  'keyframe ' + i + ' has the same composite');
+
+    assert_true(!!KeyframesA[i].marginLeft,
+                'original keyframe ' + i + ' has the valid property value');
+    assert_true(!!KeyframesB[i].marginLeft,
+                'new keyframe ' + i + ' has the valid property value');
+    assert_equals(KeyframesA[i].marginLeft, KeyframesB[i].marginLeft,
+                  'keyframe ' + i + ' has the same property value pair');
+  }
+}, 'Test copied keyframeEffectReadOnly has the same keyframes');
+
+test(function(t) {
+  var effect = new KeyframeEffectReadOnly(null, null,
+                                          { spacing: 'paced(margin-left)',
+                                            iterationComposite: 'accumulate' });
+
+  var copiedEffect = new KeyframeEffectReadOnly(effect);
+  assert_equals(copiedEffect.spacing, effect.spacing, 'same spacing');
+  assert_equals(copiedEffect.iterationComposite, effect.iterationComposite,
+                'same iterationCompositeOperation');
+  assert_equals(copiedEffect.composite, effect.composite,
+                'same compositeOperation');
+}, 'Test copied keyframeEffectReadOnly has the same keyframeEffectOptions');
+
+test(function(t) {
+  var effect = new KeyframeEffectReadOnly(null, null,
+                                          { duration: 100 * MS_PER_SEC,
+                                            delay: -1 * MS_PER_SEC,
+                                            endDelay: 2 * MS_PER_SEC,
+                                            fill: 'forwards',
+                                            iterationStart: 2,
+                                            iterations: 20,
+                                            easing: 'ease-out',
+                                            direction: 'alternate' } );
+
+  var copiedEffect = new KeyframeEffectReadOnly(effect);
+  var timingA = effect.timing;
+  var timingB = copiedEffect.timing;
+  assert_not_equals(timingA, timingB, 'different timing objects');
+  assert_equals(timingA.delay, timingB.delay, 'same delay');
+  assert_equals(timingA.endDelay, timingB.endDelay, 'same endDelay');
+  assert_equals(timingA.fill, timingB.fill, 'same fill');
+  assert_equals(timingA.iterationStart, timingB.iterationStart,
+                'same iterationStart');
+  assert_equals(timingA.iterations, timingB.iterations, 'same iterations');
+  assert_equals(timingA.duration, timingB.duration, 'same duration');
+  assert_equals(timingA.direction, timingB.direction, 'same direction');
+  assert_equals(timingA.easing, timingB.easing, 'same easing');
+}, 'Test copied keyframeEffectReadOnly has the same timing content');
+
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/resources/keyframe-utils.js b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/resources/keyframe-utils.js
index 626f0bf..8f4126c3 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/resources/keyframe-utils.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/resources/keyframe-utils.js
@@ -182,7 +182,7 @@
 var gKeyframeSequenceTests = [
   { desc:   "a one property one keyframe sequence",
     input:  [{ offset: 1, left: "10px" }],
-    output: [{ offset: null, computedOffset: 1, easing: "linear",
+    output: [{ offset: 1, computedOffset: 1, easing: "linear",
                left: "10px" }] },
   { desc:   "a one property two keyframe sequence",
     input:  [{ offset: 0, left: "10px" },
@@ -259,7 +259,7 @@
                left: "10px" }] },
   { desc:   "a single keyframe sequence with string offset",
     input:  [{ offset: '0.5', left: "10px" }],
-    output: [{ offset: 0.5, computedOffset: 1, easing: "linear",
+    output: [{ offset: 0.5, computedOffset: 0.5, easing: "linear",
                left: "10px" }] },
   { desc:   "a one property keyframe sequence with some omitted offsets",
     input:  [{ offset: 0.00, left: "10px" },
@@ -341,9 +341,9 @@
                composite: "replace", left: "10px" },
              { offset: 0.0, computedOffset: 0.0, easing: "linear",
                composite: "replace", top: "20px" },
-             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
                composite: "add", left: "30px" },
-             { offset: 0.5, computedOffset: 0.0, easing: "linear",
+             { offset: 0.5, computedOffset: 0.5, easing: "linear",
                composite: "add", top: "40px" },
              { offset: 1.0, computedOffset: 1.0, easing: "linear",
                composite: "replace", left: "50px" },
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/testcommon.js b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/testcommon.js
index 31ebdfaf..348af6e 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/testcommon.js
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/testcommon.js
@@ -174,3 +174,68 @@
     }());
   });
 }
+
+// Returns 'matrix()' or 'matrix3d()' function string generated from an array.
+function createMatrixFromArray(array) {
+  return (array.length == 16 ? 'matrix3d' : 'matrix') +
+         '(' + array.join() + ')';
+}
+
+// Returns 'matrix3d()' function string equivalent to
+// 'rotate3d(x, y, z, radian)'.
+function rotate3dToMatrix3d(x, y, z, radian) {
+  return createMatrixFromArray(rotate3dToMatrix(x, y, z, radian));
+}
+
+// Returns an array of the 4x4 matrix equivalent to 'rotate3d(x, y, z, radian)'.
+// https://www.w3.org/TR/css-transforms-1/#Rotate3dDefined
+function rotate3dToMatrix(x, y, z, radian) {
+  var sc = Math.sin(radian / 2) * Math.cos(radian / 2);
+  var sq = Math.sin(radian / 2) * Math.sin(radian / 2);
+
+  // Normalize the vector.
+  var length = Math.sqrt(x*x + y*y + z*z);
+  x /= length;
+  y /= length;
+  z /= length;
+
+  return [
+    1 - 2 * (y*y + z*z) * sq,
+    2 * (x * y * sq + z * sc),
+    2 * (x * z * sq - y * sc),
+    0,
+    2 * (x * y * sq - z * sc),
+    1 - 2 * (x*x + z*z) * sq,
+    2 * (y * z * sq + x * sc),
+    0,
+    2 * (x * z * sq + y * sc),
+    2 * (y * z * sq - x * sc),
+    1 - 2 * (x*x + y*y) * sq,
+    0,
+    0,
+    0,
+    0,
+    1
+  ];
+}
+
+// Compare matrix string like 'matrix(1, 0, 0, 1, 100, 0)' with tolerances.
+function assert_matrix_equals(actual, expected, description) {
+  var matrixRegExp = /^matrix(?:3d)*\((.+)\)/;
+  assert_regexp_match(actual, matrixRegExp,
+    'Actual value is not a matrix')
+  assert_regexp_match(expected, matrixRegExp,
+    'Expected value is not a matrix');
+
+  var actualMatrixArray =
+    actual.match(matrixRegExp)[1].split(',').map(Number);
+  var expectedMatrixArray =
+    expected.match(matrixRegExp)[1].split(',').map(Number);
+
+  assert_equals(actualMatrixArray.length, expectedMatrixArray.length,
+    'dimension of the matrix: ' + description);
+  for (var i = 0; i < actualMatrixArray.length; i++) {
+    assert_approx_equals(actualMatrixArray[i], expectedMatrixArray[i], 0.0001,
+      'expecetd ' + expected + ' but got ' + actual + ": " + description);
+  }
+}
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/timing-model/animations/current-time.html b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/timing-model/animations/current-time.html
index efc7ba7..df7228f 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/web-animations/timing-model/animations/current-time.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/web-animations/timing-model/animations/current-time.html
@@ -45,7 +45,7 @@
 }, 'The current time is unresolved when the start time is unresolved ' +
    '(and no hold time is set)');
 
-promise_test(function(t) {
+test(function(t) {
   var animation =
     new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
                   document.timeline);
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/webrtc/datachannel-emptystring.html b/third_party/WebKit/LayoutTests/imported/wpt/webrtc/datachannel-emptystring.html
index 9cd32a3b..6af436a 100644
--- a/third_party/WebKit/LayoutTests/imported/wpt/webrtc/datachannel-emptystring.html
+++ b/third_party/WebKit/LayoutTests/imported/wpt/webrtc/datachannel-emptystring.html
@@ -33,13 +33,13 @@
 
   // When the data channel is open, send an empty string message
   // followed by a message that contains the string "done".
-  var onSendChannelOpen = function (event) {
+  var onSendChannelOpen = test.step_func(function (event) {
     var msgEl = document.getElementById('msg');
     sendChannel.send('');
     msgEl.innerHTML += 'Sent: [empty string]<br>';
     sendChannel.send('done');
     msgEl.innerHTML += 'Sent: "done"<br>';
-  };
+  });
 
   // Check the messages received on the other side.
   // There should be an empty string message followed by a message that
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badleftbounds.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badleftbounds.html
new file mode 100644
index 0000000..110b847
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badleftbounds.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([{
+            source : webglCanvas,
+            leftBounds: [0, 1] // Too short
+          }]).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if provided a bad leftBounds');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badrightbounds.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badrightbounds.html
new file mode 100644
index 0000000..0ff2a673
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_badrightbounds.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([{
+            source : webglCanvas,
+            rightBounds: [0, 1, 2, 3, 4] // Too long
+          }]).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if provided a bad rightBounds');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nolayers.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nolayers.html
new file mode 100644
index 0000000..60949774
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nolayers.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([]).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if not provided any layers');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nosource.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nosource.html
new file mode 100644
index 0000000..e339134
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nosource.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([{}]).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if not provided a layer source');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_notsupported.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_notsupported.html
index 4f0f69e..5e4bd6a 100644
--- a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_notsupported.html
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_notsupported.html
@@ -19,7 +19,7 @@
         displays[0].requestPresent([{ source : webglCanvas }]).then( () => {
           asyncTest.step( () => {
             assert_unreached();
-          }, "Display should be presenting");
+          }, "Display should not be presenting");
         }, (err) => {
           asyncTest.step( () => {
             assert_false(displays[0].isPresenting);
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nowebgl.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nowebgl.html
new file mode 100644
index 0000000..f11d809
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_nowebgl.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<canvas id="canvas-2d"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var canvas2d = document.getElementById("canvas-2d");
+      var ctx = canvas2d.getContext("2d");
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([{ source : canvas2d }]).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if provided a non-webgl canvas');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_reject_toomanylayers.html b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_toomanylayers.html
new file mode 100644
index 0000000..4ca4b932
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_reject_toomanylayers.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var max_layers = displays[0].capabilities.maxLayers;
+      var layers = [];
+      for (var i = 0; i <= max_layers; ++i) {
+        layers.push({ source : webglCanvas });
+      }
+      var asyncTest = async_test(
+          "requestPresent rejects and does not present");
+      runWithUserGesture( () => {
+        displays[0].requestPresent(layers).then( () => {
+          asyncTest.step( () => {
+            assert_unreached();
+          }, "Display should not be presenting");
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_false(displays[0].isPresenting);
+          }, "requestPresent rejected and not presenting");
+        }).then( () => {
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent rejects if provided too many layers');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_repeatwithoutgesture.html b/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_repeatwithoutgesture.html
new file mode 100644
index 0000000..1f2e6d5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_repeatwithoutgesture.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  return navigator.getVRDisplays().then(displays => {
+      assert_true(displays != null);
+      assert_equals(1, displays.length);
+      var asyncTest = async_test(
+          "requestPresent resolves and actually presents");
+      runWithUserGesture( () => {
+        displays[0].requestPresent([{ source : webglCanvas }]).then( () => {
+          asyncTest.step( () => {
+            assert_true(displays[0].isPresenting);
+          }, "Display should be presenting");
+
+          // Call requestPresent again after a short delay, but without a user
+          // gesture. Should resolve because it's already presenting.
+          setTimeout(() => {
+            displays[0].requestPresent([{ source : webglCanvas }]).then( () => {
+              asyncTest.step( () => {
+                assert_true(displays[0].isPresenting);
+              }, "Display should still be presenting");
+            }, (err) => {
+              asyncTest.step( () => {
+                assert_unreached(err);
+              }, "Should never reach here");
+            }).then( () => {
+              asyncTest.done();
+            });
+          }, 100);
+
+        }, (err) => {
+          asyncTest.step( () => {
+            assert_unreached(err);
+          }, "Should never reach here");
+          asyncTest.done();
+        });
+      });
+    });
+}, [fakeDisplays['Pixel']],
+'Test requestPresent resolves without a user gesture when already presenting');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_webgl2.html b/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_webgl2.html
new file mode 100644
index 0000000..d45e05b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/vr/requestPresent_resolve_webgl2.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/mojo-helpers.js"></script>
+<script src="resources/fake-vr-displays.js"></script>
+<script src="resources/mock-vr-service.js"></script>
+<canvas id="webgl2-canvas"></canvas>
+<script src="resources/presentation-setup.js"></script>
+<script>
+let fakeDisplays = fakeVRDisplays();
+
+vr_test((service) => {
+  if (!gl) {
+    // WebGL 2 is not supported. This is legal.
+    return Promise.resolve();
+  } else {
+    return navigator.getVRDisplays().then(displays => {
+        assert_true(displays != null);
+        assert_equals(1, displays.length);
+
+        var asyncTest = async_test(
+          "requestPresent resolves and actually presents");
+        runWithUserGesture( () => {
+          displays[0].requestPresent([{ source : webglCanvas }]).then( () => {
+            asyncTest.step( () => {
+              assert_true(displays[0].isPresenting);
+            }, "Display should be presenting");
+          }, (err) => {
+            asyncTest.step( () => {
+              assert_unreached(err);
+            }, "Should never reach here");
+          }).then( () => {
+            asyncTest.done();
+          });
+        });
+      });
+  }
+}, [fakeDisplays['Pixel']],
+'Test requestPresent resolves when provided a WebGL2 canvas');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/vr/resources/fake-vr-displays.js b/third_party/WebKit/LayoutTests/vr/resources/fake-vr-displays.js
index f469e9c..ce8fb48 100644
--- a/third_party/WebKit/LayoutTests/vr/resources/fake-vr-displays.js
+++ b/third_party/WebKit/LayoutTests/vr/resources/fake-vr-displays.js
@@ -38,6 +38,7 @@
         hasPosition : false,
         hasExternalDisplay : false,
         canPresent : true,
+        maxLayers: 1
       },
       stageParameters : null,
       leftEye : {
diff --git a/third_party/WebKit/LayoutTests/vr/resources/presentation-setup.js b/third_party/WebKit/LayoutTests/vr/resources/presentation-setup.js
index 33c8269e..b024216 100644
--- a/third_party/WebKit/LayoutTests/vr/resources/presentation-setup.js
+++ b/third_party/WebKit/LayoutTests/vr/resources/presentation-setup.js
@@ -1,9 +1,14 @@
+var webgl2 = false;
 var webglCanvas = document.getElementById("webgl-canvas");
+if (!webglCanvas) {
+  webglCanvas = document.getElementById("webgl2-canvas");
+  webgl2 = true;
+}
 var glAttributes = {
   alpha : false,
   antialias : false,
 };
-var gl = webglCanvas.getContext("webgl", glAttributes);
+var gl = webglCanvas.getContext(webgl2 ? "webgl2" : "webgl", glAttributes);
 
 function runWithUserGesture(fn) {
   function thunk() {
diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
index 8a4f76b..6b0a0ed 100644
--- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
+++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
@@ -244,7 +244,7 @@
   DocumentMarker::MarkerTypeIndex markerListIndex =
       MarkerTypeToMarkerIndex(newMarker.type());
   if (!markers->at(markerListIndex)) {
-    markers->insert(markerListIndex, new MarkerList);
+    markers->at(markerListIndex) = new MarkerList;
   }
 
   Member<MarkerList>& list = markers->at(markerListIndex);
diff --git a/third_party/WebKit/Source/core/events/MouseEvent.cpp b/third_party/WebKit/Source/core/events/MouseEvent.cpp
index 6d6f1cf54..5d64a5f 100644
--- a/third_party/WebKit/Source/core/events/MouseEvent.cpp
+++ b/third_party/WebKit/Source/core/events/MouseEvent.cpp
@@ -186,6 +186,40 @@
     bool canBubble,
     bool cancelable,
     AbstractView* abstractView,
+    PlatformMouseEvent::SyntheticEventType syntheticEventType,
+    const String& region,
+    const WebMouseEvent& event)
+    : UIEventWithKeyState(
+          eventType,
+          canBubble,
+          cancelable,
+          abstractView,
+          0,
+          static_cast<PlatformEvent::Modifiers>(event.modifiers),
+          TimeTicks::FromSeconds(event.timeStampSeconds),
+          syntheticEventType == PlatformMouseEvent::FromTouch
+              ? InputDeviceCapabilities::firesTouchEventsSourceCapabilities()
+              : InputDeviceCapabilities::
+                    doesntFireTouchEventsSourceCapabilities()),
+      m_screenLocation(event.globalX, event.globalY),
+      m_movementDelta(flooredIntPoint(event.movementInRootFrame())),
+      m_positionType(syntheticEventType == PlatformMouseEvent::Positionless
+                         ? PositionType::Positionless
+                         : PositionType::Position),
+      m_button(0),
+      m_buttons(platformModifiersToButtons(event.modifiers)),
+      m_syntheticEventType(syntheticEventType),
+      m_region(region) {
+  IntPoint rootFrameCoordinates = flooredIntPoint(event.positionInRootFrame());
+  initCoordinatesFromRootFrame(rootFrameCoordinates.x(),
+                               rootFrameCoordinates.y());
+}
+
+MouseEvent::MouseEvent(
+    const AtomicString& eventType,
+    bool canBubble,
+    bool cancelable,
+    AbstractView* abstractView,
     int detail,
     int screenX,
     int screenY,
@@ -225,36 +259,7 @@
       m_region(region) {
   if (mouseEvent)
     m_mouseEvent.reset(new PlatformMouseEvent(*mouseEvent));
-
-  DoublePoint adjustedPageLocation;
-  DoubleSize scrollOffset;
-
-  LocalFrame* frame = view() && view()->isLocalDOMWindow()
-                          ? toLocalDOMWindow(view())->frame()
-                          : nullptr;
-  if (frame && hasPosition()) {
-    if (FrameView* frameView = frame->view()) {
-      adjustedPageLocation =
-          frameView->rootFrameToContents(IntPoint(windowX, windowY));
-      scrollOffset = frameView->scrollOffsetInt();
-      float scaleFactor = 1 / frame->pageZoomFactor();
-      if (scaleFactor != 1.0f) {
-        adjustedPageLocation.scale(scaleFactor, scaleFactor);
-        scrollOffset.scale(scaleFactor, scaleFactor);
-      }
-    }
-  }
-
-  m_clientLocation = adjustedPageLocation - scrollOffset;
-  m_pageLocation = adjustedPageLocation;
-
-  // Set up initial values for coordinates.
-  // Correct values are computed lazily, see computeRelativePosition.
-  m_layerLocation = m_pageLocation;
-  m_offsetLocation = m_pageLocation;
-
-  computePageLocation();
-  m_hasCachedRelativePosition = false;
+  initCoordinatesFromRootFrame(windowX, windowY);
 }
 
 MouseEvent::MouseEvent(const AtomicString& eventType,
@@ -286,6 +291,38 @@
   m_hasCachedRelativePosition = false;
 }
 
+void MouseEvent::initCoordinatesFromRootFrame(int windowX, int windowY) {
+  DoublePoint adjustedPageLocation;
+  DoubleSize scrollOffset;
+
+  LocalFrame* frame = view() && view()->isLocalDOMWindow()
+                          ? toLocalDOMWindow(view())->frame()
+                          : nullptr;
+  if (frame && hasPosition()) {
+    if (FrameView* frameView = frame->view()) {
+      adjustedPageLocation =
+          frameView->rootFrameToContents(IntPoint(windowX, windowY));
+      scrollOffset = frameView->scrollOffsetInt();
+      float scaleFactor = 1 / frame->pageZoomFactor();
+      if (scaleFactor != 1.0f) {
+        adjustedPageLocation.scale(scaleFactor, scaleFactor);
+        scrollOffset.scale(scaleFactor, scaleFactor);
+      }
+    }
+  }
+
+  m_clientLocation = adjustedPageLocation - scrollOffset;
+  m_pageLocation = adjustedPageLocation;
+
+  // Set up initial values for coordinates.
+  // Correct values are computed lazily, see computeRelativePosition.
+  m_layerLocation = m_pageLocation;
+  m_offsetLocation = m_pageLocation;
+
+  computePageLocation();
+  m_hasCachedRelativePosition = false;
+}
+
 MouseEvent::~MouseEvent() {}
 
 unsigned short MouseEvent::platformModifiersToButtons(unsigned modifiers) {
diff --git a/third_party/WebKit/Source/core/events/MouseEvent.h b/third_party/WebKit/Source/core/events/MouseEvent.h
index d554587..a6fa57b 100644
--- a/third_party/WebKit/Source/core/events/MouseEvent.h
+++ b/third_party/WebKit/Source/core/events/MouseEvent.h
@@ -199,6 +199,14 @@
              bool canBubble,
              bool cancelable,
              AbstractView*,
+             PlatformMouseEvent::SyntheticEventType,
+             const String& region,
+             const WebMouseEvent&);
+
+  MouseEvent(const AtomicString& type,
+             bool canBubble,
+             bool cancelable,
+             AbstractView*,
              int detail,
              int screenX,
              int screenY,
@@ -239,6 +247,7 @@
                               unsigned short buttons = 0);
 
   void initCoordinates(const double clientX, const double clientY);
+  void initCoordinatesFromRootFrame(int windowX, int windowY);
   void receivedTarget() final;
 
   void computePageLocation();
diff --git a/third_party/WebKit/Source/core/events/UIEventWithKeyState.h b/third_party/WebKit/Source/core/events/UIEventWithKeyState.h
index 88c6e19..43bde9fe 100644
--- a/third_party/WebKit/Source/core/events/UIEventWithKeyState.h
+++ b/third_party/WebKit/Source/core/events/UIEventWithKeyState.h
@@ -27,6 +27,7 @@
 #include "core/CoreExport.h"
 #include "core/events/EventModifierInit.h"
 #include "core/events/UIEvent.h"
+#include "platform/PlatformEvent.h"
 
 namespace blink {
 
diff --git a/third_party/WebKit/Source/core/events/WheelEvent.cpp b/third_party/WebKit/Source/core/events/WheelEvent.cpp
index 40ee82e..5f39f8b 100644
--- a/third_party/WebKit/Source/core/events/WheelEvent.cpp
+++ b/third_party/WebKit/Source/core/events/WheelEvent.cpp
@@ -25,49 +25,32 @@
 
 #include "core/clipboard/DataTransfer.h"
 #include "platform/PlatformMouseEvent.h"
-#include "platform/PlatformWheelEvent.h"
 
 namespace blink {
 
-inline static unsigned convertDeltaMode(const PlatformWheelEvent& event) {
-  return event.granularity() == ScrollByPageWheelEvent
-             ? WheelEvent::kDomDeltaPage
-             : WheelEvent::kDomDeltaPixel;
+namespace {
+
+unsigned convertDeltaMode(const WebMouseWheelEvent& event) {
+  return event.scrollByPage ? WheelEvent::kDomDeltaPage
+                            : WheelEvent::kDomDeltaPixel;
 }
 
 // Negate a long value without integer overflow.
-inline static long negateIfPossible(long value) {
+long negateIfPossible(long value) {
   if (value == LONG_MIN)
     return value;
   return -value;
 }
 
-WheelEvent* WheelEvent::create(const PlatformWheelEvent& event,
+}  // namespace
+
+WheelEvent* WheelEvent::create(const WebMouseWheelEvent& event,
                                AbstractView* view) {
-  return new WheelEvent(
-      FloatPoint(event.wheelTicksX(), event.wheelTicksY()),
-      FloatPoint(event.deltaX(), event.deltaY()), convertDeltaMode(event), view,
-      event.globalPosition(), event.position(), event.getModifiers(),
-      MouseEvent::platformModifiersToButtons(event.getModifiers()),
-      event.timestamp(), event.resendingPluginId(),
-      event.hasPreciseScrollingDeltas(),
-      static_cast<Event::RailsMode>(event.getRailsMode()), event.cancelable()
-#if OS(MACOSX)
-                                                               ,
-      static_cast<WheelEventPhase>(event.phase()),
-      static_cast<WheelEventPhase>(event.momentumPhase())
-#endif
-          );
+  return new WheelEvent(event, view);
 }
 
 WheelEvent::WheelEvent()
-    : m_deltaX(0),
-      m_deltaY(0),
-      m_deltaZ(0),
-      m_deltaMode(kDomDeltaPixel),
-      m_resendingPluginId(-1),
-      m_hasPreciseScrollingDeltas(false),
-      m_railsMode(RailsModeFree) {}
+    : m_deltaX(0), m_deltaY(0), m_deltaZ(0), m_deltaMode(kDomDeltaPixel) {}
 
 WheelEvent::WheelEvent(const AtomicString& type,
                        const WheelEventInit& initializer)
@@ -83,120 +66,26 @@
                    ? initializer.deltaY()
                    : negateIfPossible(initializer.wheelDeltaY())),
       m_deltaZ(initializer.deltaZ()),
-      m_deltaMode(initializer.deltaMode()),
-      m_resendingPluginId(-1),
-      m_hasPreciseScrollingDeltas(false),
-      m_railsMode(RailsModeFree)
-#if OS(MACOSX)
-      ,
-      m_phase(WheelEventPhaseNone),
-      m_momentumPhase(WheelEventPhaseNone)
-#endif
-{
-}
+      m_deltaMode(initializer.deltaMode()) {}
 
-WheelEvent::WheelEvent(const FloatPoint& wheelTicks,
-                       const FloatPoint& rawDelta,
-                       unsigned deltaMode,
-                       AbstractView* view,
-                       const IntPoint& screenLocation,
-                       const IntPoint& windowLocation,
-                       PlatformEvent::Modifiers modifiers,
-                       unsigned short buttons,
-                       TimeTicks platformTimeStamp,
-                       int resendingPluginId,
-                       bool hasPreciseScrollingDeltas,
-                       RailsMode railsMode,
-                       bool cancelable)
+WheelEvent::WheelEvent(const WebMouseWheelEvent& event, AbstractView* view)
     : MouseEvent(EventTypeNames::wheel,
                  true,
-                 cancelable,
+                 event.isCancelable(),
                  view,
-                 0,
-                 screenLocation.x(),
-                 screenLocation.y(),
-                 windowLocation.x(),
-                 windowLocation.y(),
-                 0,
-                 0,
-                 modifiers,
-                 0,
-                 buttons,
-                 nullptr,
-                 platformTimeStamp,
                  PlatformMouseEvent::RealOrIndistinguishable,
                  // TODO(zino): Should support canvas hit region because the
                  // wheel event is a kind of mouse event. Please see
                  // http://crbug.com/594075
                  String(),
-                 nullptr),
-      m_wheelDelta(wheelTicks.x() * TickMultiplier,
-                   wheelTicks.y() * TickMultiplier),
-      m_deltaX(-rawDelta.x()),
-      m_deltaY(-rawDelta.y()),
+                 event),
+      m_wheelDelta(event.wheelTicksX * TickMultiplier,
+                   event.wheelTicksY * TickMultiplier),
+      m_deltaX(-event.deltaXInRootFrame()),
+      m_deltaY(-event.deltaYInRootFrame()),
       m_deltaZ(0),
-      m_deltaMode(deltaMode),
-      m_resendingPluginId(resendingPluginId),
-      m_hasPreciseScrollingDeltas(hasPreciseScrollingDeltas),
-      m_railsMode(railsMode)
-#if OS(MACOSX)
-      ,
-      m_phase(WheelEventPhaseNone),
-      m_momentumPhase(WheelEventPhaseNone)
-#endif
-{
-}
-
-#if OS(MACOSX)
-WheelEvent::WheelEvent(const FloatPoint& wheelTicks,
-                       const FloatPoint& rawDelta,
-                       unsigned deltaMode,
-                       AbstractView* view,
-                       const IntPoint& screenLocation,
-                       const IntPoint& windowLocation,
-                       PlatformEvent::Modifiers modifiers,
-                       unsigned short buttons,
-                       TimeTicks platformTimeStamp,
-                       int resendingPluginId,
-                       bool hasPreciseScrollingDeltas,
-                       RailsMode railsMode,
-                       bool cancelable,
-                       WheelEventPhase phase,
-                       WheelEventPhase momentumPhase)
-    : MouseEvent(EventTypeNames::wheel,
-                 true,
-                 cancelable,
-                 view,
-                 0,
-                 screenLocation.x(),
-                 screenLocation.y(),
-                 windowLocation.x(),
-                 windowLocation.y(),
-                 0,
-                 0,
-                 modifiers,
-                 0,
-                 buttons,
-                 nullptr,
-                 platformTimeStamp,
-                 PlatformMouseEvent::RealOrIndistinguishable,
-                 // TODO(zino): Should support canvas hit region because the
-                 // wheel event is a kind of mouse event. Please see
-                 // http://crbug.com/594075
-                 String(),
-                 nullptr),
-      m_wheelDelta(wheelTicks.x() * TickMultiplier,
-                   wheelTicks.y() * TickMultiplier),
-      m_deltaX(-rawDelta.x()),
-      m_deltaY(-rawDelta.y()),
-      m_deltaZ(0),
-      m_deltaMode(deltaMode),
-      m_resendingPluginId(resendingPluginId),
-      m_hasPreciseScrollingDeltas(hasPreciseScrollingDeltas),
-      m_railsMode(railsMode),
-      m_phase(phase),
-      m_momentumPhase(momentumPhase) {}
-#endif
+      m_deltaMode(convertDeltaMode(event)),
+      m_nativeEvent(event) {}
 
 const AtomicString& WheelEvent::interfaceName() const {
   return EventNames::WheelEvent;
diff --git a/third_party/WebKit/Source/core/events/WheelEvent.h b/third_party/WebKit/Source/core/events/WheelEvent.h
index c263d43..4e7043c 100644
--- a/third_party/WebKit/Source/core/events/WheelEvent.h
+++ b/third_party/WebKit/Source/core/events/WheelEvent.h
@@ -30,23 +30,10 @@
 #include "core/events/MouseEvent.h"
 #include "core/events/WheelEventInit.h"
 #include "platform/geometry/FloatPoint.h"
+#include "public/platform/WebMouseWheelEvent.h"
 
 namespace blink {
 
-class PlatformWheelEvent;
-
-#if OS(MACOSX)
-enum WheelEventPhase {
-  WheelEventPhaseNone = 0,
-  WheelEventPhaseBegan = 1 << 0,
-  WheelEventPhaseStationary = 1 << 1,
-  WheelEventPhaseChanged = 1 << 2,
-  WheelEventPhaseEnded = 1 << 3,
-  WheelEventPhaseCancelled = 1 << 4,
-  WheelEventPhaseMayBegin = 1 << 5,
-};
-#endif
-
 class CORE_EXPORT WheelEvent final : public MouseEvent {
   DEFINE_WRAPPERTYPEINFO();
 
@@ -57,7 +44,7 @@
 
   static WheelEvent* create() { return new WheelEvent; }
 
-  static WheelEvent* create(const PlatformWheelEvent& platformEvent,
+  static WheelEvent* create(const WebMouseWheelEvent& nativeEvent,
                             AbstractView*);
 
   static WheelEvent* create(const AtomicString& type,
@@ -65,36 +52,6 @@
     return new WheelEvent(type, initializer);
   }
 
-  static WheelEvent* create(const FloatPoint& wheelTicks,
-                            const FloatPoint& rawDelta,
-                            unsigned deltaMode,
-                            AbstractView* view,
-                            const IntPoint& screenLocation,
-                            const IntPoint& windowLocation,
-                            PlatformEvent::Modifiers modifiers,
-                            unsigned short buttons,
-                            TimeTicks platformTimeStamp,
-                            int resendingPluginId,
-                            bool hasPreciseScrollingDeltas,
-                            RailsMode railsMode,
-                            bool cancelable
-#if OS(MACOSX)
-                            ,
-                            WheelEventPhase phase,
-                            WheelEventPhase momentumPhase
-#endif
-                            ) {
-    return new WheelEvent(wheelTicks, rawDelta, deltaMode, view, screenLocation,
-                          windowLocation, modifiers, buttons, platformTimeStamp,
-                          resendingPluginId, hasPreciseScrollingDeltas,
-                          railsMode, cancelable
-#if OS(MACOSX)
-                          ,
-                          phase, momentumPhase
-#endif
-                          );
-  }
-
   double deltaX() const { return m_deltaX; }  // Positive when scrolling right.
   double deltaY() const { return m_deltaY; }  // Positive when scrolling down.
   double deltaZ() const { return m_deltaZ; }
@@ -108,15 +65,6 @@
     return m_wheelDelta.y();
   }  // Deprecated, negative when scrolling down.
   unsigned deltaMode() const { return m_deltaMode; }
-  float ticksX() const {
-    return static_cast<float>(m_wheelDelta.x()) / TickMultiplier;
-  }
-  float ticksY() const {
-    return static_cast<float>(m_wheelDelta.y()) / TickMultiplier;
-  }
-  int resendingPluginId() const { return m_resendingPluginId; }
-  bool hasPreciseScrollingDeltas() const { return m_hasPreciseScrollingDeltas; }
-  RailsMode getRailsMode() const { return m_railsMode; }
 
   const AtomicString& interfaceName() const override;
   bool isMouseEvent() const override;
@@ -124,59 +72,21 @@
 
   EventDispatchMediator* createMediator() override;
 
-#if OS(MACOSX)
-  WheelEventPhase phase() const { return m_phase; }
-  WheelEventPhase momentumPhase() const { return m_momentumPhase; }
-#endif
+  const WebMouseWheelEvent& nativeEvent() const { return m_nativeEvent; }
 
   DECLARE_VIRTUAL_TRACE();
 
  private:
   WheelEvent();
   WheelEvent(const AtomicString&, const WheelEventInit&);
-  WheelEvent(const FloatPoint& wheelTicks,
-             const FloatPoint& rawDelta,
-             unsigned,
-             AbstractView*,
-             const IntPoint& screenLocation,
-             const IntPoint& windowLocation,
-             PlatformEvent::Modifiers,
-             unsigned short buttons,
-             TimeTicks platformTimeStamp,
-             int resendingPluginId,
-             bool hasPreciseScrollingDeltas,
-             RailsMode,
-             bool cancelable);
-#if OS(MACOSX)
-  WheelEvent(const FloatPoint& wheelTicks,
-             const FloatPoint& rawDelta,
-             unsigned,
-             AbstractView*,
-             const IntPoint& screenLocation,
-             const IntPoint& windowLocation,
-             PlatformEvent::Modifiers,
-             unsigned short buttons,
-             TimeTicks platformTimeStamp,
-             int resendingPluginId,
-             bool hasPreciseScrollingDeltas,
-             RailsMode,
-             bool cancelable,
-             WheelEventPhase phase,
-             WheelEventPhase momentumPhase);
-#endif
+  WheelEvent(const WebMouseWheelEvent&, AbstractView*);
 
   IntPoint m_wheelDelta;
   double m_deltaX;
   double m_deltaY;
   double m_deltaZ;
   unsigned m_deltaMode;
-  int m_resendingPluginId;
-  bool m_hasPreciseScrollingDeltas;
-  RailsMode m_railsMode;
-#if OS(MACOSX)
-  WheelEventPhase m_phase;
-  WheelEventPhase m_momentumPhase;
-#endif
+  WebMouseWheelEvent m_nativeEvent;
 };
 
 DEFINE_EVENT_TYPE_CASTS(WheelEvent);
diff --git a/third_party/WebKit/Source/core/input/EventHandler.cpp b/third_party/WebKit/Source/core/input/EventHandler.cpp
index 32a751c9..77571b8 100644
--- a/third_party/WebKit/Source/core/input/EventHandler.cpp
+++ b/third_party/WebKit/Source/core/input/EventHandler.cpp
@@ -88,7 +88,6 @@
 #include "core/style/CursorData.h"
 #include "core/svg/SVGDocumentExtensions.h"
 #include "platform/PlatformTouchEvent.h"
-#include "platform/PlatformWheelEvent.h"
 #include "platform/RuntimeEnabledFeatures.h"
 #include "platform/WindowsKeyboardCodes.h"
 #include "platform/geometry/FloatPoint.h"
@@ -98,6 +97,7 @@
 #include "platform/scroll/ScrollAnimatorBase.h"
 #include "platform/scroll/Scrollbar.h"
 #include "public/platform/WebInputEvent.h"
+#include "public/platform/WebMouseWheelEvent.h"
 #include "wtf/Assertions.h"
 #include "wtf/CurrentTime.h"
 #include "wtf/PtrUtil.h"
@@ -1270,17 +1270,18 @@
 }
 
 WebInputEventResult EventHandler::handleWheelEvent(
-    const PlatformWheelEvent& event) {
+    const WebMouseWheelEvent& event) {
 #if OS(MACOSX)
   // Filter Mac OS specific phases, usually with a zero-delta.
   // https://crbug.com/553732
-  // TODO(chongz): EventSender sends events with |PlatformWheelEventPhaseNone|,
+  // TODO(chongz): EventSender sends events with
+  // |WebMouseWheelEvent::PhaseNone|,
   // but it shouldn't.
-  const int kPlatformWheelEventPhaseNoEventMask =
-      PlatformWheelEventPhaseEnded | PlatformWheelEventPhaseCancelled |
-      PlatformWheelEventPhaseMayBegin;
-  if ((event.phase() & kPlatformWheelEventPhaseNoEventMask) ||
-      (event.momentumPhase() & kPlatformWheelEventPhaseNoEventMask))
+  const int kWheelEventPhaseNoEventMask = WebMouseWheelEvent::PhaseEnded |
+                                          WebMouseWheelEvent::PhaseCancelled |
+                                          WebMouseWheelEvent::PhaseMayBegin;
+  if ((event.phase & kWheelEventPhaseNoEventMask) ||
+      (event.momentumPhase & kWheelEventPhaseNoEventMask))
     return WebInputEventResult::NotHandled;
 #endif
   Document* doc = m_frame->document();
@@ -1292,7 +1293,8 @@
   if (!view)
     return WebInputEventResult::NotHandled;
 
-  LayoutPoint vPoint = view->rootFrameToContents(event.position());
+  LayoutPoint vPoint =
+      view->rootFrameToContents(flooredIntPoint(event.positionInRootFrame()));
 
   HitTestRequest request(HitTestRequest::ReadOnly);
   HitTestResult result(request, vPoint);
diff --git a/third_party/WebKit/Source/core/input/EventHandler.h b/third_party/WebKit/Source/core/input/EventHandler.h
index 0d6cca7..9f29bc4d 100644
--- a/third_party/WebKit/Source/core/input/EventHandler.h
+++ b/third_party/WebKit/Source/core/input/EventHandler.h
@@ -70,12 +70,12 @@
 class Node;
 class OptionalCursor;
 class PlatformTouchEvent;
-class PlatformWheelEvent;
 class ScrollableArea;
 class Scrollbar;
 class SelectionController;
 class TextEvent;
 class WebGestureEvent;
+class WebMouseWheelEvent;
 
 class CORE_EXPORT EventHandler final
     : public GarbageCollectedFinalized<EventHandler> {
@@ -149,7 +149,7 @@
 
   WebInputEventResult handleMousePressEvent(const PlatformMouseEvent&);
   WebInputEventResult handleMouseReleaseEvent(const PlatformMouseEvent&);
-  WebInputEventResult handleWheelEvent(const PlatformWheelEvent&);
+  WebInputEventResult handleWheelEvent(const WebMouseWheelEvent&);
 
   // Called on the local root frame exactly once per gesture event.
   WebInputEventResult handleGestureEvent(const WebGestureEvent&);
diff --git a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
index 7189ef4..53356ee 100644
--- a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.cpp
@@ -336,36 +336,31 @@
 }
 
 int LayoutThemeDefault::menuListArrowWidthInDIP() const {
+  if (m_menuListArrowWidthInDIP > 0)
+    return m_menuListArrowWidthInDIP;
   int width = Platform::current()
                   ->themeEngine()
                   ->getSize(WebThemeEngine::PartScrollbarUpArrow)
                   .width;
-  return width > 0 ? width : 15;
+  const_cast<LayoutThemeDefault*>(this)->m_menuListArrowWidthInDIP =
+      width > 0 ? width : 15;
+  return m_menuListArrowWidthInDIP;
 }
 
 float LayoutThemeDefault::clampedMenuListArrowPaddingSize(
     const HostWindow* host,
     const ComputedStyle& style) const {
-  if (m_cachedMenuListArrowPaddingSize > 0 &&
-      style.effectiveZoom() == m_cachedMenuListArrowZoomLevel)
-    return m_cachedMenuListArrowPaddingSize;
-  m_cachedMenuListArrowZoomLevel = style.effectiveZoom();
   int originalSize = menuListArrowWidthInDIP();
   int scaledSize =
       host ? host->windowToViewportScalar(originalSize) : originalSize;
   // The result should not be samller than the scrollbar thickness in order to
   // secure space for scrollbar in popup.
   float deviceScale = 1.0f * scaledSize / originalSize;
-  float size;
-  if (m_cachedMenuListArrowZoomLevel < deviceScale) {
-    size = scaledSize;
-  } else {
-    // The value should be zoomed though scrollbars aren't scaled by zoom.
-    // crbug.com/432795.
-    size = originalSize * m_cachedMenuListArrowZoomLevel;
-  }
-  m_cachedMenuListArrowPaddingSize = size;
-  return size;
+  if (style.effectiveZoom() < deviceScale)
+    return scaledSize;
+  // The value should be zoomed though scrollbars aren't scaled by zoom.
+  // crbug.com/432795.
+  return originalSize * style.effectiveZoom();
 }
 
 // static
diff --git a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
index bec946e..498edfd 100644
--- a/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
+++ b/third_party/WebKit/Source/core/layout/LayoutThemeDefault.h
@@ -159,9 +159,7 @@
   static unsigned m_inactiveSelectionForegroundColor;
 
   ThemePainterDefault m_painter;
-  // Cached values for crbug.com/673754.
-  mutable float m_cachedMenuListArrowZoomLevel = 0;
-  mutable float m_cachedMenuListArrowPaddingSize = 0;
+  int m_menuListArrowWidthInDIP = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp b/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
index 2213c24..372dd6a4 100644
--- a/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
+++ b/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
@@ -226,13 +226,14 @@
 
   // Emulate starting a real load, but don't expect any "real"
   // WebURLLoaderClient callbacks.
-  ImageResource* cachedImage = ImageResource::create(ResourceRequest(testURL));
-  cachedImage->setIdentifier(createUniqueIdentifier());
-  fetcher->startLoad(cachedImage);
+  ImageResource* imageResource =
+      ImageResource::create(ResourceRequest(testURL));
+  imageResource->setIdentifier(createUniqueIdentifier());
+  fetcher->startLoad(imageResource);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
-  EXPECT_EQ(Resource::Pending, cachedImage->getStatus());
+      new MockImageResourceClient(imageResource);
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
 
   // Send the multipart response. No image or data buffer is created. Note that
   // the response must be routed through ResourceLoader to ensure the load is
@@ -240,58 +241,58 @@
   ResourceResponse multipartResponse(KURL(), "multipart/x-mixed-replace", 0,
                                      nullAtom, String());
   multipartResponse.setMultipartBoundary("boundary", strlen("boundary"));
-  cachedImage->loader()->didReceiveResponse(
+  imageResource->loader()->didReceiveResponse(
       WrappedResourceResponse(multipartResponse), nullptr);
-  EXPECT_FALSE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->getContent()->hasImage());
   EXPECT_EQ(0, client->imageChangedCount());
   EXPECT_FALSE(client->notifyFinishedCalled());
-  EXPECT_EQ("multipart/x-mixed-replace", cachedImage->response().mimeType());
+  EXPECT_EQ("multipart/x-mixed-replace", imageResource->response().mimeType());
 
   const char firstPart[] =
       "--boundary\n"
       "Content-Type: image/svg+xml\n\n";
-  cachedImage->appendData(firstPart, strlen(firstPart));
+  imageResource->appendData(firstPart, strlen(firstPart));
   // Send the response for the first real part. No image or data buffer is
   // created.
-  EXPECT_FALSE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->getContent()->hasImage());
   EXPECT_EQ(0, client->imageChangedCount());
   EXPECT_FALSE(client->notifyFinishedCalled());
-  EXPECT_EQ("image/svg+xml", cachedImage->response().mimeType());
+  EXPECT_EQ("image/svg+xml", imageResource->response().mimeType());
 
   const char secondPart[] =
       "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect "
       "width='1' height='1' fill='green'/></svg>\n";
   // The first bytes arrive. The data buffer is created, but no image is
   // created.
-  cachedImage->appendData(secondPart, strlen(secondPart));
-  EXPECT_TRUE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->getContent()->hasImage());
+  imageResource->appendData(secondPart, strlen(secondPart));
+  EXPECT_TRUE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->getContent()->hasImage());
   EXPECT_EQ(0, client->imageChangedCount());
   EXPECT_FALSE(client->notifyFinishedCalled());
 
   // Add a client to check an assertion error doesn't happen
   // (crbug.com/630983).
   Persistent<MockImageResourceClient> client2 =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
   EXPECT_EQ(0, client2->imageChangedCount());
   EXPECT_FALSE(client2->notifyFinishedCalled());
 
   const char thirdPart[] = "--boundary";
-  cachedImage->appendData(thirdPart, strlen(thirdPart));
-  ASSERT_TRUE(cachedImage->resourceBuffer());
-  EXPECT_EQ(strlen(secondPart) - 1, cachedImage->resourceBuffer()->size());
+  imageResource->appendData(thirdPart, strlen(thirdPart));
+  ASSERT_TRUE(imageResource->resourceBuffer());
+  EXPECT_EQ(strlen(secondPart) - 1, imageResource->resourceBuffer()->size());
 
   // This part finishes. The image is created, callbacks are sent, and the data
   // buffer is cleared.
-  cachedImage->loader()->didFinishLoading(0.0, 0, 0);
-  EXPECT_TRUE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->height());
+  imageResource->loader()->didFinishLoading(0.0, 0, 0);
+  EXPECT_TRUE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
   EXPECT_EQ(1, client->imageChangedCount());
   EXPECT_TRUE(client->notifyFinishedCalled());
   EXPECT_EQ(1, client2->imageChangedCount());
@@ -306,94 +307,95 @@
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
 
   // Emulate starting a real load.
-  ImageResource* cachedImage = ImageResource::create(ResourceRequest(testURL));
-  cachedImage->setIdentifier(createUniqueIdentifier());
+  ImageResource* imageResource =
+      ImageResource::create(ResourceRequest(testURL));
+  imageResource->setIdentifier(createUniqueIdentifier());
 
-  fetcher->startLoad(cachedImage);
-  memoryCache()->add(cachedImage);
+  fetcher->startLoad(imageResource);
+  memoryCache()->add(imageResource);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
-  EXPECT_EQ(Resource::Pending, cachedImage->getStatus());
+      new MockImageResourceClient(imageResource);
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
 
   // The load should still be alive, but a timer should be started to cancel the
   // load inside removeClient().
   client->removeAsClient();
-  EXPECT_EQ(Resource::Pending, cachedImage->getStatus());
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
   EXPECT_TRUE(memoryCache()->resourceForURL(testURL));
 
   // Trigger the cancel timer, ensure the load was cancelled and the resource
   // was evicted from the cache.
   blink::testing::runPendingTasks();
-  EXPECT_EQ(Resource::LoadError, cachedImage->getStatus());
+  EXPECT_EQ(Resource::LoadError, imageResource->getStatus());
   EXPECT_FALSE(memoryCache()->resourceForURL(testURL));
 }
 
 TEST(ImageResourceTest, DecodedDataRemainsWhileHasClients) {
-  ImageResource* cachedImage = ImageResource::create(ResourceRequest());
-  cachedImage->setStatus(Resource::Pending);
+  ImageResource* imageResource = ImageResource::create(ResourceRequest());
+  imageResource->setStatus(Resource::Pending);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
 
   // Send the image response.
-  cachedImage->responseReceived(
+  imageResource->responseReceived(
       ResourceResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom,
                        String()),
       nullptr);
 
-  cachedImage->responseReceived(
+  imageResource->responseReceived(
       ResourceResponse(KURL(), "image/jpeg", sizeof(kJpegImage), nullAtom,
                        String()),
       nullptr);
-  cachedImage->appendData(reinterpret_cast<const char*>(kJpegImage),
-                          sizeof(kJpegImage));
-  EXPECT_NE(0u, cachedImage->encodedSizeMemoryUsageForTesting());
-  cachedImage->finish();
-  EXPECT_EQ(0u, cachedImage->encodedSizeMemoryUsageForTesting());
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  imageResource->appendData(reinterpret_cast<const char*>(kJpegImage),
+                            sizeof(kJpegImage));
+  EXPECT_NE(0u, imageResource->encodedSizeMemoryUsageForTesting());
+  imageResource->finish();
+  EXPECT_EQ(0u, imageResource->encodedSizeMemoryUsageForTesting());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_TRUE(client->notifyFinishedCalled());
 
   // The prune comes when the ImageResource still has clients. The image should
   // not be deleted.
-  cachedImage->prune();
-  EXPECT_TRUE(cachedImage->isAlive());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  imageResource->prune();
+  EXPECT_TRUE(imageResource->isAlive());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
 
   // The ImageResource no longer has clients. The decoded image data should be
   // deleted by prune.
   client->removeAsClient();
-  cachedImage->prune();
-  EXPECT_FALSE(cachedImage->isAlive());
-  EXPECT_TRUE(cachedImage->getContent()->hasImage());
-  // TODO(hajimehoshi): Should check cachedImage doesn't have decoded image
+  imageResource->prune();
+  EXPECT_FALSE(imageResource->isAlive());
+  EXPECT_TRUE(imageResource->getContent()->hasImage());
+  // TODO(hajimehoshi): Should check imageResource doesn't have decoded image
   // data.
 }
 
 TEST(ImageResourceTest, UpdateBitmapImages) {
-  ImageResource* cachedImage = ImageResource::create(ResourceRequest());
-  cachedImage->setStatus(Resource::Pending);
+  ImageResource* imageResource = ImageResource::create(ResourceRequest());
+  imageResource->setStatus(Resource::Pending);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
 
   // Send the image response.
-  cachedImage->responseReceived(
+  imageResource->responseReceived(
       ResourceResponse(KURL(), "image/jpeg", sizeof(kJpegImage), nullAtom,
                        String()),
       nullptr);
-  cachedImage->appendData(reinterpret_cast<const char*>(kJpegImage),
-                          sizeof(kJpegImage));
-  cachedImage->finish();
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  imageResource->appendData(reinterpret_cast<const char*>(kJpegImage),
+                            sizeof(kJpegImage));
+  imageResource->finish();
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(2, client->imageChangedCount());
   EXPECT_TRUE(client->notifyFinishedCalled());
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
 }
 
 TEST(ImageResourceTest, ReloadIfLoFiOrPlaceholderAfterFinished) {
@@ -401,11 +403,11 @@
   ScopedRegisteredURL scopedRegisteredURL(testURL);
   ResourceRequest request = ResourceRequest(testURL);
   request.setLoFiState(WebURLRequest::LoFiOn);
-  ImageResource* cachedImage = ImageResource::create(request);
-  cachedImage->setStatus(Resource::Pending);
+  ImageResource* imageResource = ImageResource::create(request);
+  imageResource->setStatus(Resource::Pending);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
   ResourceFetcher* fetcher =
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
 
@@ -415,13 +417,13 @@
   resourceResponse.addHTTPHeaderField("chrome-proxy-content-transform",
                                       "empty-image");
 
-  cachedImage->responseReceived(resourceResponse, nullptr);
-  cachedImage->appendData(reinterpret_cast<const char*>(kJpegImage),
-                          sizeof(kJpegImage));
-  cachedImage->finish();
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  imageResource->responseReceived(resourceResponse, nullptr);
+  imageResource->appendData(reinterpret_cast<const char*>(kJpegImage),
+                            sizeof(kJpegImage));
+  imageResource->finish();
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(2, client->imageChangedCount());
   EXPECT_EQ(1, client->imageNotifyFinishedCount());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnLastImageChanged());
@@ -429,27 +431,28 @@
   EXPECT_TRUE(client->notifyFinishedCalled());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnNotifyFinished());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnImageNotifyFinished());
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
 
   // Call reloadIfLoFiOrPlaceholderImage() after the image has finished loading.
-  cachedImage->reloadIfLoFiOrPlaceholderImage(fetcher, Resource::kReloadAlways);
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  EXPECT_FALSE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->getContent()->hasImage());
+  imageResource->reloadIfLoFiOrPlaceholderImage(fetcher,
+                                                Resource::kReloadAlways);
+  EXPECT_FALSE(imageResource->errorOccurred());
+  EXPECT_FALSE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->getContent()->hasImage());
   EXPECT_EQ(3, client->imageChangedCount());
   EXPECT_EQ(1, client->imageNotifyFinishedCount());
 
-  cachedImage->loader()->didReceiveResponse(
+  imageResource->loader()->didReceiveResponse(
       WrappedResourceResponse(resourceResponse), nullptr);
-  cachedImage->loader()->didReceiveData(
+  imageResource->loader()->didReceiveData(
       reinterpret_cast<const char*>(kJpegImage2), sizeof(kJpegImage2));
-  cachedImage->loader()->didFinishLoading(0.0, sizeof(kJpegImage2),
-                                          sizeof(kJpegImage2));
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  imageResource->loader()->didFinishLoading(0.0, sizeof(kJpegImage2),
+                                            sizeof(kJpegImage2));
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(sizeof(kJpegImage2), client->encodedSizeOnLastImageChanged());
   EXPECT_TRUE(client->notifyFinishedCalled());
 
@@ -457,9 +460,9 @@
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnNotifyFinished());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnImageNotifyFinished());
 
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->height());
 }
 
 TEST(ImageResourceTest, ReloadIfLoFiOrPlaceholderDuringFetch) {
@@ -472,62 +475,63 @@
   ResourceFetcher* fetcher =
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
 
-  ImageResource* cachedImage = ImageResource::fetch(fetchRequest, fetcher);
+  ImageResource* imageResource = ImageResource::fetch(fetchRequest, fetcher);
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
 
   // Send the image response.
   ResourceResponse initialResourceResponse(
       testURL, "image/jpeg", sizeof(kJpegImage), nullAtom, String());
   initialResourceResponse.addHTTPHeaderField("chrome-proxy", "q=low");
 
-  cachedImage->loader()->didReceiveResponse(
+  imageResource->loader()->didReceiveResponse(
       WrappedResourceResponse(initialResourceResponse));
-  cachedImage->loader()->didReceiveData(
+  imageResource->loader()->didReceiveData(
       reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage));
 
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(1, client->imageChangedCount());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnLastImageChanged());
   EXPECT_FALSE(client->notifyFinishedCalled());
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(1, cachedImage->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
 
   // Call reloadIfLoFiOrPlaceholderImage() while the image is still loading.
-  cachedImage->reloadIfLoFiOrPlaceholderImage(fetcher, Resource::kReloadAlways);
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  EXPECT_FALSE(cachedImage->resourceBuffer());
-  EXPECT_FALSE(cachedImage->getContent()->hasImage());
+  imageResource->reloadIfLoFiOrPlaceholderImage(fetcher,
+                                                Resource::kReloadAlways);
+  EXPECT_FALSE(imageResource->errorOccurred());
+  EXPECT_FALSE(imageResource->resourceBuffer());
+  EXPECT_FALSE(imageResource->getContent()->hasImage());
   EXPECT_EQ(2, client->imageChangedCount());
   EXPECT_EQ(0U, client->encodedSizeOnLastImageChanged());
   // The client should not have been notified of completion yet, since the image
   // is still loading.
   EXPECT_FALSE(client->notifyFinishedCalled());
 
-  cachedImage->loader()->didReceiveResponse(
+  imageResource->loader()->didReceiveResponse(
       WrappedResourceResponse(ResourceResponse(
           testURL, "image/jpeg", sizeof(kJpegImage2), nullAtom, String())),
       nullptr);
-  cachedImage->loader()->didReceiveData(
+  imageResource->loader()->didReceiveData(
       reinterpret_cast<const char*>(kJpegImage2), sizeof(kJpegImage2));
-  cachedImage->loader()->didFinishLoading(0.0, sizeof(kJpegImage2),
-                                          sizeof(kJpegImage2));
+  imageResource->loader()->didFinishLoading(0.0, sizeof(kJpegImage2),
+                                            sizeof(kJpegImage2));
 
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(sizeof(kJpegImage2), client->encodedSizeOnLastImageChanged());
   // The client should have been notified of completion only after the reload
   // completed.
   EXPECT_TRUE(client->notifyFinishedCalled());
   EXPECT_EQ(sizeof(kJpegImage2), client->encodedSizeOnNotifyFinished());
   EXPECT_EQ(sizeof(kJpegImage2), client->encodedSizeOnImageNotifyFinished());
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->height());
 }
 
 TEST(ImageResourceTest, ReloadIfLoFiOrPlaceholderForPlaceholder) {
@@ -538,12 +542,13 @@
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
   FetchRequest request(testURL, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(request, fetcher);
+  ImageResource* imageResource = ImageResource::fetch(request, fetcher);
   EXPECT_EQ(FetchRequest::AllowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ("bytes=0-2047", image->resourceRequest().httpHeaderField("range"));
+  EXPECT_EQ("bytes=0-2047",
+            imageResource->resourceRequest().httpHeaderField("range"));
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
   ResourceResponse response(testURL, "image/jpeg",
                             kJpegImageSubrangeWithDimensionsLength, nullAtom,
@@ -552,24 +557,30 @@
   response.setHTTPHeaderField(
       "content-range", buildContentRange(kJpegImageSubrangeWithDimensionsLength,
                                          sizeof(kJpegImage)));
-  image->loader()->didReceiveResponse(WrappedResourceResponse(response));
-  image->loader()->didReceiveData(reinterpret_cast<const char*>(kJpegImage),
-                                  kJpegImageSubrangeWithDimensionsLength);
-  image->loader()->didFinishLoading(0.0, kJpegImageSubrangeWithDimensionsLength,
-                                    kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(response));
+  imageResource->loader()->didReceiveData(
+      reinterpret_cast<const char*>(kJpegImage),
+      kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didFinishLoading(
+      0.0, kJpegImageSubrangeWithDimensionsLength,
+      kJpegImageSubrangeWithDimensionsLength);
 
-  EXPECT_EQ(Resource::Cached, image->getStatus());
-  EXPECT_TRUE(image->isPlaceholder());
+  EXPECT_EQ(Resource::Cached, imageResource->getStatus());
+  EXPECT_TRUE(imageResource->isPlaceholder());
 
-  image->reloadIfLoFiOrPlaceholderImage(fetcher, Resource::kReloadAlways);
+  imageResource->reloadIfLoFiOrPlaceholderImage(fetcher,
+                                                Resource::kReloadAlways);
 
-  EXPECT_EQ(Resource::Pending, image->getStatus());
-  EXPECT_FALSE(image->isPlaceholder());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_EQ(static_cast<int>(WebCachePolicy::BypassingCache),
-            static_cast<int>(image->resourceRequest().getCachePolicy()));
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
+  EXPECT_FALSE(imageResource->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_EQ(
+      static_cast<int>(WebCachePolicy::BypassingCache),
+      static_cast<int>(imageResource->resourceRequest().getCachePolicy()));
 
-  image->loader()->cancel();
+  imageResource->loader()->cancel();
 }
 
 TEST(ImageResourceTest, SVGImage) {
@@ -850,15 +861,15 @@
   ResourceFetcher* fetcher =
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
   FetchRequest request(testURL, FetchInitiatorInfo());
-  ImageResource* cachedImage = ImageResource::fetch(request, fetcher);
+  ImageResource* imageResource = ImageResource::fetch(request, fetcher);
 
-  cachedImage->loader()->didReceiveResponse(
+  imageResource->loader()->didReceiveResponse(
       WrappedResourceResponse(
           ResourceResponse(testURL, "image/jpeg", 18, nullAtom, String())),
       nullptr);
-  cachedImage->loader()->didReceiveData("notactuallyanimage", 18);
-  EXPECT_EQ(Resource::DecodeError, cachedImage->getStatus());
-  EXPECT_FALSE(cachedImage->isLoading());
+  imageResource->loader()->didReceiveData("notactuallyanimage", 18);
+  EXPECT_EQ(Resource::DecodeError, imageResource->getStatus());
+  EXPECT_FALSE(imageResource->isLoading());
 }
 
 TEST(ImageResourceTest, FetchDisallowPlaceholder) {
@@ -866,36 +877,38 @@
   ScopedRegisteredURL scopedRegisteredURL(testURL);
 
   FetchRequest request(testURL, FetchInitiatorInfo());
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::DisallowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_FALSE(imageResource->isPlaceholder());
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
-  image->loader()->didReceiveResponse(WrappedResourceResponse(ResourceResponse(
-      testURL, "image/jpeg", sizeof(kJpegImage), nullAtom, String())));
-  image->loader()->didReceiveData(reinterpret_cast<const char*>(kJpegImage),
-                                  sizeof(kJpegImage));
-  image->loader()->didFinishLoading(0.0, sizeof(kJpegImage),
-                                    sizeof(kJpegImage));
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(ResourceResponse(
+          testURL, "image/jpeg", sizeof(kJpegImage), nullAtom, String())));
+  imageResource->loader()->didReceiveData(
+      reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage));
+  imageResource->loader()->didFinishLoading(0.0, sizeof(kJpegImage),
+                                            sizeof(kJpegImage));
 
-  EXPECT_EQ(Resource::Cached, image->getStatus());
-  EXPECT_EQ(sizeof(kJpegImage), image->encodedSize());
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ(Resource::Cached, imageResource->getStatus());
+  EXPECT_EQ(sizeof(kJpegImage), imageResource->encodedSize());
+  EXPECT_FALSE(imageResource->isPlaceholder());
   EXPECT_LT(0, client->imageChangedCount());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnLastImageChanged());
   EXPECT_TRUE(client->notifyFinishedCalled());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnNotifyFinished());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnImageNotifyFinished());
 
-  ASSERT_TRUE(image->getContent()->hasImage());
-  EXPECT_EQ(1, image->getContent()->getImage()->width());
-  EXPECT_EQ(1, image->getContent()->getImage()->height());
-  EXPECT_TRUE(image->getContent()->getImage()->isBitmapImage());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderDataURL) {
@@ -905,13 +918,14 @@
                                 sizeof(kJpegImage)));
   FetchRequest request(testURL, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::DisallowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_FALSE(imageResource->isPlaceholder());
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderPostRequest) {
@@ -921,15 +935,16 @@
   resourceRequest.setHTTPMethod("POST");
   FetchRequest request(resourceRequest, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::DisallowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_FALSE(imageResource->isPlaceholder());
 
-  image->loader()->cancel();
+  imageResource->loader()->cancel();
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderExistingRangeHeader) {
@@ -939,15 +954,16 @@
   resourceRequest.setHTTPHeaderField("range", "bytes=128-255");
   FetchRequest request(resourceRequest, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::DisallowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ("bytes=128-255", image->resourceRequest().httpHeaderField("range"));
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ("bytes=128-255",
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_FALSE(imageResource->isPlaceholder());
 
-  image->loader()->cancel();
+  imageResource->loader()->cancel();
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderSuccessful) {
@@ -956,15 +972,16 @@
 
   FetchRequest request(testURL, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::AllowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ("bytes=0-2047", image->resourceRequest().httpHeaderField("range"));
-  EXPECT_TRUE(image->isPlaceholder());
+  EXPECT_EQ("bytes=0-2047",
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_TRUE(imageResource->isPlaceholder());
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
   ResourceResponse response(testURL, "image/jpeg",
                             kJpegImageSubrangeWithDimensionsLength, nullAtom,
@@ -973,15 +990,19 @@
   response.setHTTPHeaderField(
       "content-range", buildContentRange(kJpegImageSubrangeWithDimensionsLength,
                                          sizeof(kJpegImage)));
-  image->loader()->didReceiveResponse(WrappedResourceResponse(response));
-  image->loader()->didReceiveData(reinterpret_cast<const char*>(kJpegImage),
-                                  kJpegImageSubrangeWithDimensionsLength);
-  image->loader()->didFinishLoading(0.0, kJpegImageSubrangeWithDimensionsLength,
-                                    kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(response));
+  imageResource->loader()->didReceiveData(
+      reinterpret_cast<const char*>(kJpegImage),
+      kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didFinishLoading(
+      0.0, kJpegImageSubrangeWithDimensionsLength,
+      kJpegImageSubrangeWithDimensionsLength);
 
-  EXPECT_EQ(Resource::Cached, image->getStatus());
-  EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength, image->encodedSize());
-  EXPECT_TRUE(image->isPlaceholder());
+  EXPECT_EQ(Resource::Cached, imageResource->getStatus());
+  EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength,
+            imageResource->encodedSize());
+  EXPECT_TRUE(imageResource->isPlaceholder());
   EXPECT_LT(0, client->imageChangedCount());
   EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength,
             client->encodedSizeOnLastImageChanged());
@@ -991,11 +1012,11 @@
   EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength,
             client->encodedSizeOnImageNotifyFinished());
 
-  ASSERT_TRUE(image->getContent()->hasImage());
-  EXPECT_EQ(1, image->getContent()->getImage()->width());
-  EXPECT_EQ(1, image->getContent()->getImage()->height());
-  EXPECT_FALSE(image->getContent()->getImage()->isBitmapImage());
-  EXPECT_FALSE(image->getContent()->getImage()->isSVGImage());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isSVGImage());
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderUnsuccessful) {
@@ -1004,42 +1025,47 @@
 
   FetchRequest request(testURL, FetchInitiatorInfo());
   request.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(
+  ImageResource* imageResource = ImageResource::fetch(
       request,
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create()));
   EXPECT_EQ(FetchRequest::AllowPlaceholder,
             request.placeholderImageRequestType());
-  EXPECT_EQ("bytes=0-2047", image->resourceRequest().httpHeaderField("range"));
-  EXPECT_TRUE(image->isPlaceholder());
+  EXPECT_EQ("bytes=0-2047",
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_TRUE(imageResource->isPlaceholder());
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
   const char kBadData[] = "notanimageresponse";
 
-  image->loader()->didReceiveResponse(WrappedResourceResponse(ResourceResponse(
-      testURL, "image/jpeg", sizeof(kBadData), nullAtom, String())));
-  image->loader()->didReceiveData(kBadData, sizeof(kBadData));
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(ResourceResponse(
+          testURL, "image/jpeg", sizeof(kBadData), nullAtom, String())));
+  imageResource->loader()->didReceiveData(kBadData, sizeof(kBadData));
 
   // The dimensions could not be extracted, so the full original image should be
   // loading.
-  EXPECT_EQ(Resource::Pending, image->getStatus());
-  EXPECT_FALSE(image->isPlaceholder());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_EQ(static_cast<int>(WebCachePolicy::BypassingCache),
-            static_cast<int>(image->resourceRequest().getCachePolicy()));
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
+  EXPECT_FALSE(imageResource->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_EQ(
+      static_cast<int>(WebCachePolicy::BypassingCache),
+      static_cast<int>(imageResource->resourceRequest().getCachePolicy()));
   EXPECT_FALSE(client->notifyFinishedCalled());
   EXPECT_EQ(0, client->imageNotifyFinishedCount());
 
-  image->loader()->didReceiveResponse(WrappedResourceResponse(ResourceResponse(
-      testURL, "image/jpeg", sizeof(kJpegImage), nullAtom, String())));
-  image->loader()->didReceiveData(reinterpret_cast<const char*>(kJpegImage),
-                                  sizeof(kJpegImage));
-  image->loader()->didFinishLoading(0.0, sizeof(kJpegImage),
-                                    sizeof(kJpegImage));
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(ResourceResponse(
+          testURL, "image/jpeg", sizeof(kJpegImage), nullAtom, String())));
+  imageResource->loader()->didReceiveData(
+      reinterpret_cast<const char*>(kJpegImage), sizeof(kJpegImage));
+  imageResource->loader()->didFinishLoading(0.0, sizeof(kJpegImage),
+                                            sizeof(kJpegImage));
 
-  EXPECT_EQ(Resource::Cached, image->getStatus());
-  EXPECT_EQ(sizeof(kJpegImage), image->encodedSize());
-  EXPECT_FALSE(image->isPlaceholder());
+  EXPECT_EQ(Resource::Cached, imageResource->getStatus());
+  EXPECT_EQ(sizeof(kJpegImage), imageResource->encodedSize());
+  EXPECT_FALSE(imageResource->isPlaceholder());
   EXPECT_LT(0, client->imageChangedCount());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnLastImageChanged());
   EXPECT_TRUE(client->notifyFinishedCalled());
@@ -1047,10 +1073,10 @@
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnNotifyFinished());
   EXPECT_EQ(sizeof(kJpegImage), client->encodedSizeOnImageNotifyFinished());
 
-  ASSERT_TRUE(image->getContent()->hasImage());
-  EXPECT_EQ(1, image->getContent()->getImage()->width());
-  EXPECT_EQ(1, image->getContent()->getImage()->height());
-  EXPECT_TRUE(image->getContent()->getImage()->isBitmapImage());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(1, imageResource->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderThenDisallowPlaceholder) {
@@ -1061,22 +1087,25 @@
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
   FetchRequest placeholderRequest(testURL, FetchInitiatorInfo());
   placeholderRequest.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(placeholderRequest, fetcher);
+  ImageResource* imageResource =
+      ImageResource::fetch(placeholderRequest, fetcher);
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
   FetchRequest nonPlaceholderRequest(testURL, FetchInitiatorInfo());
-  ImageResource* secondImage =
+  ImageResource* secondImageResource =
       ImageResource::fetch(nonPlaceholderRequest, fetcher);
-  EXPECT_EQ(image, secondImage);
-  EXPECT_EQ(Resource::Pending, image->getStatus());
-  EXPECT_FALSE(image->isPlaceholder());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_EQ(static_cast<int>(WebCachePolicy::UseProtocolCachePolicy),
-            static_cast<int>(image->resourceRequest().getCachePolicy()));
+  EXPECT_EQ(imageResource, secondImageResource);
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
+  EXPECT_FALSE(imageResource->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_EQ(
+      static_cast<int>(WebCachePolicy::UseProtocolCachePolicy),
+      static_cast<int>(imageResource->resourceRequest().getCachePolicy()));
   EXPECT_FALSE(client->notifyFinishedCalled());
 
-  image->loader()->cancel();
+  imageResource->loader()->cancel();
 }
 
 TEST(ImageResourceTest,
@@ -1088,9 +1117,10 @@
       ResourceFetcher::create(ImageResourceTestMockFetchContext::create());
   FetchRequest placeholderRequest(testURL, FetchInitiatorInfo());
   placeholderRequest.setAllowImagePlaceholder();
-  ImageResource* image = ImageResource::fetch(placeholderRequest, fetcher);
+  ImageResource* imageResource =
+      ImageResource::fetch(placeholderRequest, fetcher);
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(image);
+      new MockImageResourceClient(imageResource);
 
   ResourceResponse response(testURL, "image/jpeg",
                             kJpegImageSubrangeWithDimensionsLength, nullAtom,
@@ -1099,29 +1129,35 @@
   response.setHTTPHeaderField(
       "content-range", buildContentRange(kJpegImageSubrangeWithDimensionsLength,
                                          sizeof(kJpegImage)));
-  image->loader()->didReceiveResponse(WrappedResourceResponse(response));
-  image->loader()->didReceiveData(reinterpret_cast<const char*>(kJpegImage),
-                                  kJpegImageSubrangeWithDimensionsLength);
-  image->loader()->didFinishLoading(0.0, kJpegImageSubrangeWithDimensionsLength,
-                                    kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didReceiveResponse(
+      WrappedResourceResponse(response));
+  imageResource->loader()->didReceiveData(
+      reinterpret_cast<const char*>(kJpegImage),
+      kJpegImageSubrangeWithDimensionsLength);
+  imageResource->loader()->didFinishLoading(
+      0.0, kJpegImageSubrangeWithDimensionsLength,
+      kJpegImageSubrangeWithDimensionsLength);
 
-  EXPECT_EQ(Resource::Cached, image->getStatus());
-  EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength, image->encodedSize());
-  EXPECT_TRUE(image->isPlaceholder());
+  EXPECT_EQ(Resource::Cached, imageResource->getStatus());
+  EXPECT_EQ(kJpegImageSubrangeWithDimensionsLength,
+            imageResource->encodedSize());
+  EXPECT_TRUE(imageResource->isPlaceholder());
   EXPECT_LT(0, client->imageChangedCount());
   EXPECT_TRUE(client->notifyFinishedCalled());
 
   FetchRequest nonPlaceholderRequest(testURL, FetchInitiatorInfo());
-  ImageResource* secondImage =
+  ImageResource* secondImageResource =
       ImageResource::fetch(nonPlaceholderRequest, fetcher);
-  EXPECT_EQ(image, secondImage);
-  EXPECT_EQ(Resource::Pending, image->getStatus());
-  EXPECT_FALSE(image->isPlaceholder());
-  EXPECT_EQ(nullAtom, image->resourceRequest().httpHeaderField("range"));
-  EXPECT_EQ(static_cast<int>(WebCachePolicy::UseProtocolCachePolicy),
-            static_cast<int>(image->resourceRequest().getCachePolicy()));
+  EXPECT_EQ(imageResource, secondImageResource);
+  EXPECT_EQ(Resource::Pending, imageResource->getStatus());
+  EXPECT_FALSE(imageResource->isPlaceholder());
+  EXPECT_EQ(nullAtom,
+            imageResource->resourceRequest().httpHeaderField("range"));
+  EXPECT_EQ(
+      static_cast<int>(WebCachePolicy::UseProtocolCachePolicy),
+      static_cast<int>(imageResource->resourceRequest().getCachePolicy()));
 
-  image->loader()->cancel();
+  imageResource->loader()->cancel();
 }
 
 TEST(ImageResourceTest, PeriodicFlushTest) {
@@ -1130,29 +1166,29 @@
   URLTestHelpers::registerMockedURLLoad(testURL, "cancelTest.html",
                                         "text/html");
   ResourceRequest request = ResourceRequest(testURL);
-  ImageResource* cachedImage = ImageResource::create(request);
-  cachedImage->setStatus(Resource::Pending);
+  ImageResource* imageResource = ImageResource::create(request);
+  imageResource->setStatus(Resource::Pending);
 
   Persistent<MockImageResourceClient> client =
-      new MockImageResourceClient(cachedImage);
+      new MockImageResourceClient(imageResource);
 
   // Send the image response.
   ResourceResponse resourceResponse(KURL(), "image/jpeg", sizeof(kJpegImage2),
                                     nullAtom, String());
   resourceResponse.addHTTPHeaderField("chrome-proxy", "q=low");
 
-  cachedImage->responseReceived(resourceResponse, nullptr);
+  imageResource->responseReceived(resourceResponse, nullptr);
 
   // This is number is sufficiently large amount of bytes necessary for the
   // image to be created (since the size is known). This was determined by
   // appending one byte at a time (with flushes) until the image was decoded.
   size_t meaningfulImageSize = 280;
-  cachedImage->appendData(reinterpret_cast<const char*>(kJpegImage2),
-                          meaningfulImageSize);
+  imageResource->appendData(reinterpret_cast<const char*>(kJpegImage2),
+                            meaningfulImageSize);
   size_t bytesSent = meaningfulImageSize;
 
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  EXPECT_TRUE(cachedImage->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  EXPECT_TRUE(imageResource->getContent()->hasImage());
   EXPECT_EQ(1, client->imageChangedCount());
 
   platform.runForPeriodSeconds(1.);
@@ -1160,8 +1196,8 @@
 
   // Sanity check that we created an image after appending |meaningfulImageSize|
   // bytes just once.
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
   EXPECT_EQ(1, client->imageChangedCount());
 
   for (int flushCount = 1; flushCount <= 3; ++flushCount) {
@@ -1172,11 +1208,11 @@
     // increases.
     for (int i = 0; i < 5; ++i) {
       SCOPED_TRACE(i);
-      cachedImage->appendData(
+      imageResource->appendData(
           reinterpret_cast<const char*>(kJpegImage2) + bytesSent, 1);
 
-      EXPECT_FALSE(cachedImage->errorOccurred());
-      ASSERT_TRUE(cachedImage->getContent()->hasImage());
+      EXPECT_FALSE(imageResource->errorOccurred());
+      ASSERT_TRUE(imageResource->getContent()->hasImage());
       EXPECT_EQ(flushCount, client->imageChangedCount());
 
       ++bytesSent;
@@ -1187,25 +1223,25 @@
   // Increasing time by a large number only causes one extra flush.
   platform.runForPeriodSeconds(10.);
   platform.advanceClockSeconds(10.);
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(4, client->imageChangedCount());
 
   // Append the rest of the data and finish (which causes another flush).
-  cachedImage->appendData(
+  imageResource->appendData(
       reinterpret_cast<const char*>(kJpegImage2) + bytesSent,
       sizeof(kJpegImage2) - bytesSent);
-  cachedImage->finish();
+  imageResource->finish();
 
-  EXPECT_FALSE(cachedImage->errorOccurred());
-  ASSERT_TRUE(cachedImage->getContent()->hasImage());
-  EXPECT_FALSE(cachedImage->getContent()->getImage()->isNull());
+  EXPECT_FALSE(imageResource->errorOccurred());
+  ASSERT_TRUE(imageResource->getContent()->hasImage());
+  EXPECT_FALSE(imageResource->getContent()->getImage()->isNull());
   EXPECT_EQ(5, client->imageChangedCount());
   EXPECT_TRUE(client->notifyFinishedCalled());
-  EXPECT_TRUE(cachedImage->getContent()->getImage()->isBitmapImage());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->width());
-  EXPECT_EQ(50, cachedImage->getContent()->getImage()->height());
+  EXPECT_TRUE(imageResource->getContent()->getImage()->isBitmapImage());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->width());
+  EXPECT_EQ(50, imageResource->getContent()->getImage()->height());
 
   WTF::setTimeFunctionsForTesting(nullptr);
 }
diff --git a/third_party/WebKit/Source/core/page/scrolling/ScrollingCoordinator.h b/third_party/WebKit/Source/core/page/scrolling/ScrollingCoordinator.h
index 3642ae1..fa93940 100644
--- a/third_party/WebKit/Source/core/page/scrolling/ScrollingCoordinator.h
+++ b/third_party/WebKit/Source/core/page/scrolling/ScrollingCoordinator.h
@@ -28,7 +28,6 @@
 
 #include "core/CoreExport.h"
 #include "core/paint/LayerHitTestRects.h"
-#include "platform/PlatformWheelEvent.h"
 #include "platform/geometry/IntRect.h"
 #include "platform/heap/Handle.h"
 #include "platform/scroll/MainThreadScrollingReason.h"
diff --git a/third_party/WebKit/Source/devtools/package.json b/third_party/WebKit/Source/devtools/package.json
index 6d3b5125..aba895be 100644
--- a/third_party/WebKit/Source/devtools/package.json
+++ b/third_party/WebKit/Source/devtools/package.json
@@ -6,6 +6,7 @@
     "chrome": "node scripts/chrome_debug_launcher/launch_chrome.js",
     "server": "node scripts/hosted_mode/server.js",
     "test": "node scripts/npm_test.js",
+    "debug-test": "node scripts/npm_test.js --debug-devtools",
     "lint": "eslint front_end",
     "format": "node scripts/format.js",
     "closure": "python scripts/compile_frontend.py"
diff --git a/third_party/WebKit/Source/devtools/readme.md b/third_party/WebKit/Source/devtools/readme.md
index 7895561..3ea053d 100644
--- a/third_party/WebKit/Source/devtools/readme.md
+++ b/third_party/WebKit/Source/devtools/readme.md
@@ -58,7 +58,7 @@
 * `npm test -- --fetch-content-shell` - even if you're using a full chromium checkout and have a compiled content shell, this will fetch a pre-compiled content shell. This is useful if you haven't compiled your content shell recently.
 * `npm test -- -f --child-processes=16` - pass in additional flags to the test harness
 * `npm test -- inspector/sources inspector/console` - run specific tests
-* `npm test -- inspector/cookie-resource-match.html --debug-devtools` - debug a specific test (non-bundled & minified). You can use "-d" as a shorthand for "--debug-devtools".
+* `npm test -- inspector/cookie-resource-match.html --debug-devtools` OR `npm run debug-test inspector/cookie-resource-match.html` - debug a specific test (non-bundled & minified). You can use "-d" as a shorthand for "--debug-devtools".
 
 #### Development
 * All devtools commits: [View the log], [RSS feed] or [@DevToolsCommits] on Twitter
diff --git a/third_party/WebKit/Source/devtools/scripts/npm_test.js b/third_party/WebKit/Source/devtools/scripts/npm_test.js
index 4522399..b493f0c7a 100644
--- a/third_party/WebKit/Source/devtools/scripts/npm_test.js
+++ b/third_party/WebKit/Source/devtools/scripts/npm_test.js
@@ -233,6 +233,8 @@
     ]);
     if (useDebugDevtools) {
         testArgs.push("--additional-driver-flag=--debug-devtools");
+    } else {
+        console.log("TIP: You can debug a test using: npm run debug-test inspector/test-name.html")
     }
     if (IS_DEBUG_ENABLED) {
         testArgs.push("--additional-driver-flag=--remote-debugging-port=9222");
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn
index d7edb9e..aa663aa 100644
--- a/third_party/WebKit/Source/platform/BUILD.gn
+++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -319,7 +319,6 @@
     "PlatformResourceLoader.h",
     "PlatformTouchEvent.h",
     "PlatformTouchPoint.h",
-    "PlatformWheelEvent.h",
     "PluginScriptForbiddenScope.cpp",
     "PluginScriptForbiddenScope.h",
     "PopupMenu.h",
@@ -355,6 +354,8 @@
     "WebFrameScheduler.h",
     "WebGestureEvent.cpp",
     "WebIconSizesParser.cpp",
+    "WebMouseEvent.cpp",
+    "WebMouseWheelEvent.cpp",
     "WebScheduler.cpp",
     "WebTaskRunner.cpp",
     "WebTaskRunner.h",
diff --git a/third_party/WebKit/Source/platform/PlatformEvent.h b/third_party/WebKit/Source/platform/PlatformEvent.h
index 7621db6..473a2de 100644
--- a/third_party/WebKit/Source/platform/PlatformEvent.h
+++ b/third_party/WebKit/Source/platform/PlatformEvent.h
@@ -43,9 +43,6 @@
     MouseReleased,
     MouseScroll,
 
-    // PlatformWheelEvent
-    Wheel,
-
     // PlatformTouchEvent
     TouchStart,
     TouchMove,
diff --git a/third_party/WebKit/Source/platform/PlatformWheelEvent.h b/third_party/WebKit/Source/platform/PlatformWheelEvent.h
deleted file mode 100644
index 794fc19..0000000
--- a/third_party/WebKit/Source/platform/PlatformWheelEvent.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef PlatformWheelEvent_h
-#define PlatformWheelEvent_h
-
-#include "platform/PlatformEvent.h"
-#include "platform/PlatformMouseEvent.h"
-#include "platform/geometry/IntPoint.h"
-
-namespace blink {
-
-enum PlatformWheelEventGranularity {
-  // Indicates that the wheel event should scroll an entire page. In this case
-  // WebCore's built in paging behavior is used to page up and down (you get the
-  // same behavior as if the user was clicking in a scrollbar track to page up
-  // or page down).
-  ScrollByPageWheelEvent,
-  // A fine-grained event that specifies the precise number of pixels to scroll.
-  // It is sent directly by MacBook touchpads on OS X, and synthesized in other
-  // cases where platforms generate line-by-line scrolling events.
-  ScrollByPixelWheelEvent,
-};
-
-#if OS(MACOSX)
-enum PlatformWheelEventPhase {
-  PlatformWheelEventPhaseNone = 0,
-  PlatformWheelEventPhaseBegan = 1 << 0,
-  PlatformWheelEventPhaseStationary = 1 << 1,
-  PlatformWheelEventPhaseChanged = 1 << 2,
-  PlatformWheelEventPhaseEnded = 1 << 3,
-  PlatformWheelEventPhaseCancelled = 1 << 4,
-  PlatformWheelEventPhaseMayBegin = 1 << 5,
-};
-#endif
-
-class PlatformWheelEvent : public PlatformMouseEvent {
- public:
-  PlatformWheelEvent()
-      : PlatformMouseEvent(PlatformEvent::Wheel),
-        m_deltaX(0),
-        m_deltaY(0),
-        m_wheelTicksX(0),
-        m_wheelTicksY(0),
-        m_granularity(ScrollByPixelWheelEvent),
-        m_hasPreciseScrollingDeltas(false),
-        m_resendingPluginId(-1),
-        m_railsMode(RailsModeFree),
-        m_dispatchType(Blocking)
-#if OS(MACOSX)
-        ,
-        m_phase(PlatformWheelEventPhaseNone),
-        m_momentumPhase(PlatformWheelEventPhaseNone)
-#endif
-  {
-  }
-
-  float deltaX() const { return m_deltaX; }
-  float deltaY() const { return m_deltaY; }
-
-  float wheelTicksX() const { return m_wheelTicksX; }
-  float wheelTicksY() const { return m_wheelTicksY; }
-
-  PlatformWheelEventGranularity granularity() const { return m_granularity; }
-
-  bool hasPreciseScrollingDeltas() const { return m_hasPreciseScrollingDeltas; }
-  void setHasPreciseScrollingDeltas(bool b) { m_hasPreciseScrollingDeltas = b; }
-  int resendingPluginId() const { return m_resendingPluginId; }
-  RailsMode getRailsMode() const { return m_railsMode; }
-  DispatchType dispatchType() const { return m_dispatchType; }
-  bool cancelable() const { return m_dispatchType == PlatformEvent::Blocking; }
-#if OS(MACOSX)
-  PlatformWheelEventPhase phase() const { return m_phase; }
-  PlatformWheelEventPhase momentumPhase() const { return m_momentumPhase; }
-#endif
-
- protected:
-  float m_deltaX;
-  float m_deltaY;
-  float m_wheelTicksX;
-  float m_wheelTicksY;
-  PlatformWheelEventGranularity m_granularity;
-  bool m_hasPreciseScrollingDeltas;
-  int m_resendingPluginId;
-  RailsMode m_railsMode;
-  DispatchType m_dispatchType;
-#if OS(MACOSX)
-  PlatformWheelEventPhase m_phase;
-  PlatformWheelEventPhase m_momentumPhase;
-#endif
-};
-
-}  // namespace blink
-
-#endif  // PlatformWheelEvent_h
diff --git a/third_party/WebKit/Source/platform/WebMouseEvent.cpp b/third_party/WebKit/Source/platform/WebMouseEvent.cpp
new file mode 100644
index 0000000..67cf41d8
--- /dev/null
+++ b/third_party/WebKit/Source/platform/WebMouseEvent.cpp
@@ -0,0 +1,26 @@
+// 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 "public/platform/WebInputEvent.h"
+
+namespace blink {
+
+WebFloatPoint WebMouseEvent::movementInRootFrame() const {
+  return WebFloatPoint((movementX / m_frameScale), (movementY / m_frameScale));
+}
+
+WebFloatPoint WebMouseEvent::positionInRootFrame() const {
+  return WebFloatPoint((x / m_frameScale) + m_frameTranslate.x,
+                       (y / m_frameScale) + m_frameTranslate.y);
+}
+
+void WebMouseEvent::flattenTransformSelf() {
+  x = (x / m_frameScale) + m_frameTranslate.x;
+  y = (y / m_frameScale) + m_frameTranslate.y;
+  m_frameTranslate.x = 0;
+  m_frameTranslate.y = 0;
+  m_frameScale = 1;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/WebMouseWheelEvent.cpp b/third_party/WebKit/Source/platform/WebMouseWheelEvent.cpp
new file mode 100644
index 0000000..e3085cb
--- /dev/null
+++ b/third_party/WebKit/Source/platform/WebMouseWheelEvent.cpp
@@ -0,0 +1,25 @@
+// 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 "public/platform/WebMouseWheelEvent.h"
+
+namespace blink {
+
+float WebMouseWheelEvent::deltaXInRootFrame() const {
+  return deltaX / m_frameScale;
+}
+
+float WebMouseWheelEvent::deltaYInRootFrame() const {
+  return deltaY / m_frameScale;
+}
+
+WebMouseWheelEvent WebMouseWheelEvent::flattenTransform() const {
+  WebMouseWheelEvent result = *this;
+  result.deltaX /= result.m_frameScale;
+  result.deltaY /= result.m_frameScale;
+  result.flattenTransformSelf();
+  return result;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
index 26a5dba..2a66531 100644
--- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
@@ -30,6 +30,14 @@
 
 namespace blink {
 
+#define EXPECT_BLINK_FLOAT_RECT_EQ(expected, actual)         \
+  do {                                                       \
+    EXPECT_FLOAT_EQ((expected).x(), (actual).x());           \
+    EXPECT_FLOAT_EQ((expected).y(), (actual).y());           \
+    EXPECT_FLOAT_EQ((expected).width(), (actual).width());   \
+    EXPECT_FLOAT_EQ((expected).height(), (actual).height()); \
+  } while (false)
+
 using ::blink::testing::createOpacityOnlyEffect;
 using ::testing::Pointee;
 
@@ -1466,7 +1474,7 @@
 
   EXPECT_TRUE(pendingLayer.backfaceHidden);
   EXPECT_TRUE(pendingLayer.knownToBeOpaque);
-  EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds);
+  EXPECT_BLINK_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds);
 
   PaintChunk chunk2;
   chunk2.properties.propertyTreeState = chunk1.properties.propertyTreeState;
@@ -1478,7 +1486,7 @@
   EXPECT_TRUE(pendingLayer.backfaceHidden);
   // Bounds not equal to one PaintChunk.
   EXPECT_FALSE(pendingLayer.knownToBeOpaque);
-  EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 40, 60), pendingLayer.bounds);
+  EXPECT_BLINK_FLOAT_RECT_EQ(FloatRect(0, 0, 40, 60), pendingLayer.bounds);
 
   PaintChunk chunk3;
   chunk3.properties.propertyTreeState = chunk1.properties.propertyTreeState;
@@ -1489,7 +1497,7 @@
 
   EXPECT_TRUE(pendingLayer.backfaceHidden);
   EXPECT_FALSE(pendingLayer.knownToBeOpaque);
-  EXPECT_FLOAT_RECT_EQ(FloatRect(-5, -25, 45, 85), pendingLayer.bounds);
+  EXPECT_BLINK_FLOAT_RECT_EQ(FloatRect(-5, -25, 45, 85), pendingLayer.bounds);
 }
 
 TEST_F(PaintArtifactCompositorTestWithPropertyTrees, PendingLayerWithGeometry) {
@@ -1507,7 +1515,7 @@
 
   PaintArtifactCompositor::PendingLayer pendingLayer(chunk1);
 
-  EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds);
+  EXPECT_BLINK_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds);
 
   PaintChunk chunk2;
   chunk2.properties.propertyTreeState = chunk1.properties.propertyTreeState;
@@ -1516,7 +1524,7 @@
   GeometryMapper geometryMapper;
   pendingLayer.add(chunk2, &geometryMapper);
 
-  EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 70, 85), pendingLayer.bounds);
+  EXPECT_BLINK_FLOAT_RECT_EQ(FloatRect(0, 0, 70, 85), pendingLayer.bounds);
 }
 
 TEST_F(PaintArtifactCompositorTestWithPropertyTrees, PendingLayerKnownOpaque) {
diff --git a/third_party/WebKit/Source/platform/mac/ScrollAnimatorMac.mm b/third_party/WebKit/Source/platform/mac/ScrollAnimatorMac.mm
index 434bd1ef..6b2452b 100644
--- a/third_party/WebKit/Source/platform/mac/ScrollAnimatorMac.mm
+++ b/third_party/WebKit/Source/platform/mac/ScrollAnimatorMac.mm
@@ -25,7 +25,6 @@
 
 #include "platform/mac/ScrollAnimatorMac.h"
 
-#include "platform/PlatformWheelEvent.h"
 #include "platform/Timer.h"
 #include "platform/animation/TimingFunction.h"
 #include "platform/geometry/FloatRect.h"
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h
index 758431c..fc004bd 100644
--- a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h
+++ b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h
@@ -32,7 +32,6 @@
 #define ScrollAnimatorBase_h
 
 #include "platform/PlatformExport.h"
-#include "platform/PlatformWheelEvent.h"
 #include "platform/heap/Handle.h"
 #include "platform/scroll/ScrollAnimatorCompositorCoordinator.h"
 #include "platform/scroll/ScrollTypes.h"
diff --git a/third_party/WebKit/Source/web/AssertMatchingEnums.cpp b/third_party/WebKit/Source/web/AssertMatchingEnums.cpp
index 5c4b237..6b6c2e83 100644
--- a/third_party/WebKit/Source/web/AssertMatchingEnums.cpp
+++ b/third_party/WebKit/Source/web/AssertMatchingEnums.cpp
@@ -574,32 +574,6 @@
 STATIC_ASSERT_ENUM(WebMediaPlayer::ReadyStateHaveEnoughData,
                    HTMLMediaElement::kHaveEnoughData);
 
-#if OS(MACOSX)
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseNone, PlatformWheelEventPhaseNone);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseBegan,
-                   PlatformWheelEventPhaseBegan);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseStationary,
-                   PlatformWheelEventPhaseStationary);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseChanged,
-                   PlatformWheelEventPhaseChanged);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseEnded,
-                   PlatformWheelEventPhaseEnded);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseCancelled,
-                   PlatformWheelEventPhaseCancelled);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseMayBegin,
-                   PlatformWheelEventPhaseMayBegin);
-
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseNone, WheelEventPhaseNone);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseBegan, WheelEventPhaseBegan);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseStationary,
-                   WheelEventPhaseStationary);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseChanged, WheelEventPhaseChanged);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseEnded, WheelEventPhaseEnded);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseCancelled,
-                   WheelEventPhaseCancelled);
-STATIC_ASSERT_ENUM(WebMouseWheelEvent::PhaseMayBegin, WheelEventPhaseMayBegin);
-#endif
-
 STATIC_ASSERT_ENUM(WebScrollbar::Horizontal, HorizontalScrollbar);
 STATIC_ASSERT_ENUM(WebScrollbar::Vertical, VerticalScrollbar);
 
diff --git a/third_party/WebKit/Source/web/InspectorOverlay.cpp b/third_party/WebKit/Source/web/InspectorOverlay.cpp
index 92ab7a9..a81df3fb 100644
--- a/third_party/WebKit/Source/web/InspectorOverlay.cpp
+++ b/third_party/WebKit/Source/web/InspectorOverlay.cpp
@@ -237,14 +237,14 @@
   if (WebInputEvent::isGestureEventType(inputEvent.type) &&
       inputEvent.type == WebInputEvent::GestureTap) {
     // We only have a use for gesture tap.
-    WebGestureEvent scaledEvent = TransformWebGestureEvent(
+    WebGestureEvent transformedEvent = TransformWebGestureEvent(
         m_frameImpl->frameView(),
         static_cast<const WebGestureEvent&>(inputEvent));
-    handled = handleGestureEvent(scaledEvent);
+    handled = handleGestureEvent(transformedEvent);
     if (handled)
       return true;
 
-    overlayMainFrame()->eventHandler().handleGestureEvent(scaledEvent);
+    overlayMainFrame()->eventHandler().handleGestureEvent(transformedEvent);
   }
   if (WebInputEvent::isMouseEventType(inputEvent.type) &&
       inputEvent.type != WebInputEvent::MouseEnter) {
@@ -295,11 +295,11 @@
   }
 
   if (inputEvent.type == WebInputEvent::MouseWheel) {
-    PlatformWheelEvent wheelEvent = PlatformWheelEventBuilder(
+    WebMouseWheelEvent transformedEvent = TransformWebMouseWheelEvent(
         m_frameImpl->frameView(),
         static_cast<const WebMouseWheelEvent&>(inputEvent));
-    handled = overlayMainFrame()->eventHandler().handleWheelEvent(wheelEvent) !=
-              WebInputEventResult::NotHandled;
+    handled = overlayMainFrame()->eventHandler().handleWheelEvent(
+                  transformedEvent) != WebInputEventResult::NotHandled;
   }
 
   return handled;
diff --git a/third_party/WebKit/Source/web/PageWidgetDelegate.cpp b/third_party/WebKit/Source/web/PageWidgetDelegate.cpp
index e16d29ea..7afa9fc8 100644
--- a/third_party/WebKit/Source/web/PageWidgetDelegate.cpp
+++ b/third_party/WebKit/Source/web/PageWidgetDelegate.cpp
@@ -250,8 +250,9 @@
 WebInputEventResult PageWidgetEventHandler::handleMouseWheel(
     LocalFrame& mainFrame,
     const WebMouseWheelEvent& event) {
-  return mainFrame.eventHandler().handleWheelEvent(
-      PlatformWheelEventBuilder(mainFrame.view(), event));
+  WebMouseWheelEvent transformedEvent =
+      TransformWebMouseWheelEvent(mainFrame.view(), event);
+  return mainFrame.eventHandler().handleWheelEvent(transformedEvent);
 }
 
 WebInputEventResult PageWidgetEventHandler::handleTouchEvent(
diff --git a/third_party/WebKit/Source/web/WebInputEventConversion.cpp b/third_party/WebKit/Source/web/WebInputEventConversion.cpp
index 96c62ee..5269947 100644
--- a/third_party/WebKit/Source/web/WebInputEventConversion.cpp
+++ b/third_party/WebKit/Source/web/WebInputEventConversion.cpp
@@ -210,38 +210,15 @@
   }
 }
 
-// PlatformWheelEventBuilder --------------------------------------------------
-
-PlatformWheelEventBuilder::PlatformWheelEventBuilder(
+WebMouseWheelEvent TransformWebMouseWheelEvent(
     Widget* widget,
-    const WebMouseWheelEvent& e) {
-  m_position = widget->convertFromRootFrame(flooredIntPoint(
-      convertHitPointToRootFrame(widget, FloatPoint(e.x, e.y))));
-  m_globalPosition = IntPoint(e.globalX, e.globalY);
-  m_deltaX = scaleDeltaToWindow(widget, e.deltaX);
-  m_deltaY = scaleDeltaToWindow(widget, e.deltaY);
-  m_wheelTicksX = e.wheelTicksX;
-  m_wheelTicksY = e.wheelTicksY;
-  m_granularity =
-      e.scrollByPage ? ScrollByPageWheelEvent : ScrollByPixelWheelEvent;
-
-  m_type = PlatformEvent::Wheel;
-
-  m_timestamp = TimeTicks::FromSeconds(e.timeStampSeconds);
-  m_modifiers = e.modifiers;
-  m_dispatchType = toPlatformDispatchType(e.dispatchType);
-
-  m_hasPreciseScrollingDeltas = e.hasPreciseScrollingDeltas;
-  m_resendingPluginId = e.resendingPluginId;
-  m_railsMode = static_cast<PlatformEvent::RailsMode>(e.railsMode);
-#if OS(MACOSX)
-  m_phase = static_cast<PlatformWheelEventPhase>(e.phase);
-  m_momentumPhase = static_cast<PlatformWheelEventPhase>(e.momentumPhase);
-#endif
+    const WebMouseWheelEvent& event) {
+  WebMouseWheelEvent result = event;
+  result.setFrameScale(frameScale(widget));
+  result.setFrameTranslate(frameTranslation(widget));
+  return result;
 }
 
-// PlatformGestureEventBuilder -----------------------------------------------
-
 WebGestureEvent TransformWebGestureEvent(Widget* widget,
                                          const WebGestureEvent& event) {
   WebGestureEvent result = event;
@@ -484,31 +461,6 @@
   pointerType = WebPointerProperties::PointerType::Touch;
 }
 
-WebMouseWheelEventBuilder::WebMouseWheelEventBuilder(
-    const Widget* widget,
-    const LayoutItem layoutItem,
-    const WheelEvent& event) {
-  if (event.type() != EventTypeNames::wheel &&
-      event.type() != EventTypeNames::mousewheel)
-    return;
-  type = WebInputEvent::MouseWheel;
-  updateWebMouseEventFromCoreMouseEvent(event, widget, layoutItem, *this);
-  deltaX = -event.deltaX();
-  deltaY = -event.deltaY();
-  wheelTicksX = event.ticksX();
-  wheelTicksY = event.ticksY();
-  scrollByPage = event.deltaMode() == WheelEvent::kDomDeltaPage;
-  resendingPluginId = event.resendingPluginId();
-  railsMode = static_cast<RailsMode>(event.getRailsMode());
-  hasPreciseScrollingDeltas = event.hasPreciseScrollingDeltas();
-  dispatchType = event.cancelable() ? WebInputEvent::Blocking
-                                    : WebInputEvent::EventNonBlocking;
-#if OS(MACOSX)
-  phase = static_cast<Phase>(event.phase());
-  momentumPhase = static_cast<Phase>(event.momentumPhase());
-#endif
-}
-
 WebKeyboardEventBuilder::WebKeyboardEventBuilder(const KeyboardEvent& event) {
   if (const WebKeyboardEvent* webEvent = event.keyEvent()) {
     *static_cast<WebKeyboardEvent*>(this) = *webEvent;
diff --git a/third_party/WebKit/Source/web/WebInputEventConversion.h b/third_party/WebKit/Source/web/WebInputEventConversion.h
index 495f6ed..8aeb4c8 100644
--- a/third_party/WebKit/Source/web/WebInputEventConversion.h
+++ b/third_party/WebKit/Source/web/WebInputEventConversion.h
@@ -33,7 +33,6 @@
 
 #include "platform/PlatformMouseEvent.h"
 #include "platform/PlatformTouchEvent.h"
-#include "platform/PlatformWheelEvent.h"
 #include "platform/scroll/ScrollTypes.h"
 #include "public/platform/WebInputEvent.h"
 #include "public/platform/WebMouseWheelEvent.h"
@@ -51,7 +50,6 @@
 class WebMouseEvent;
 class WebKeyboardEvent;
 class WebTouchEvent;
-class WheelEvent;
 class Widget;
 
 // These classes are used to convert from WebInputEvent subclasses to
@@ -63,12 +61,6 @@
   PlatformMouseEventBuilder(Widget*, const WebMouseEvent&);
 };
 
-class WEB_EXPORT PlatformWheelEventBuilder
-    : NON_EXPORTED_BASE(public PlatformWheelEvent) {
- public:
-  PlatformWheelEventBuilder(Widget*, const WebMouseWheelEvent&);
-};
-
 // Converts a WebTouchPoint to a PlatformTouchPoint.
 class WEB_EXPORT PlatformTouchPointBuilder
     : NON_EXPORTED_BASE(public PlatformTouchPoint) {
@@ -94,14 +86,6 @@
   WebMouseEventBuilder(const Widget*, const LayoutItem, const TouchEvent&);
 };
 
-// Converts a WheelEvent to a corresponding WebMouseWheelEvent.
-// If the event mapping fails, the event type will be set to Undefined.
-class WEB_EXPORT WebMouseWheelEventBuilder
-    : NON_EXPORTED_BASE(public WebMouseWheelEvent) {
- public:
-  WebMouseWheelEventBuilder(const Widget*, const LayoutItem, const WheelEvent&);
-};
-
 // Converts a KeyboardEvent to a corresponding WebKeyboardEvent.
 // NOTE: For KeyboardEvent, this is only implemented for keydown,
 // keyup, and keypress. If the event mapping fails, the event type will be set
@@ -125,6 +109,8 @@
 // and translation.
 WEB_EXPORT WebGestureEvent TransformWebGestureEvent(Widget*,
                                                     const WebGestureEvent&);
+WEB_EXPORT WebMouseWheelEvent
+TransformWebMouseWheelEvent(Widget*, const WebMouseWheelEvent&);
 
 Vector<PlatformMouseEvent> WEB_EXPORT
 createPlatformMouseEventVector(Widget*,
diff --git a/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp b/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
index 16da91b..4c6d88d4b 100644
--- a/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
+++ b/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
@@ -758,13 +758,17 @@
 }
 
 void WebPluginContainerImpl::handleWheelEvent(WheelEvent* event) {
-  WebMouseWheelEventBuilder webEvent(
-      this, LayoutItem(m_element->layoutObject()), *event);
-  if (webEvent.type == WebInputEvent::Undefined)
-    return;
+  WebFloatPoint absoluteRootFrameLocation =
+      event->nativeEvent().positionInRootFrame();
+  IntPoint localPoint =
+      roundedIntPoint(m_element->layoutObject()->absoluteToLocal(
+          absoluteRootFrameLocation, UseTransforms));
+  WebMouseWheelEvent translatedEvent = event->nativeEvent().flattenTransform();
+  translatedEvent.x = localPoint.x();
+  translatedEvent.y = localPoint.y();
 
   WebCursorInfo cursorInfo;
-  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
+  if (m_webPlugin->handleInputEvent(translatedEvent, cursorInfo) !=
       WebInputEventResult::NotHandled)
     event->setDefaultHandled();
 }
diff --git a/third_party/WebKit/Source/web/tests/WebInputEventConversionTest.cpp b/third_party/WebKit/Source/web/tests/WebInputEventConversionTest.cpp
index e666873..8cfba8b 100644
--- a/third_party/WebKit/Source/web/tests/WebInputEventConversionTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebInputEventConversionTest.cpp
@@ -937,11 +937,14 @@
     webMouseWheelEvent.globalX = 10;
     webMouseWheelEvent.globalY = 10;
 
-    PlatformWheelEventBuilder platformWheelBuilder(view, webMouseWheelEvent);
-    EXPECT_EQ(5 + visualOffset.x(), platformWheelBuilder.position().x());
-    EXPECT_EQ(5 + visualOffset.y(), platformWheelBuilder.position().y());
-    EXPECT_EQ(10, platformWheelBuilder.globalPosition().x());
-    EXPECT_EQ(10, platformWheelBuilder.globalPosition().y());
+    WebMouseWheelEvent scaledMouseWheelEvent =
+        TransformWebMouseWheelEvent(view, webMouseWheelEvent);
+    IntPoint position =
+        flooredIntPoint(scaledMouseWheelEvent.positionInRootFrame());
+    EXPECT_EQ(5 + visualOffset.x(), position.x());
+    EXPECT_EQ(5 + visualOffset.y(), position.y());
+    EXPECT_EQ(10, scaledMouseWheelEvent.globalX);
+    EXPECT_EQ(10, scaledMouseWheelEvent.globalY);
   }
 
   {
@@ -1108,178 +1111,4 @@
   }
 }
 
-TEST(WebInputEventConversionTest, WebMouseWheelEventBuilder) {
-  const std::string baseURL("http://www.test7.com/");
-  const std::string fileName("fixed_layout.html");
-
-  URLTestHelpers::registerMockedURLFromBaseURL(
-      WebString::fromUTF8(baseURL.c_str()),
-      WebString::fromUTF8("fixed_layout.html"));
-  FrameTestHelpers::WebViewHelper webViewHelper;
-  WebViewImpl* webViewImpl =
-      webViewHelper.initializeAndLoad(baseURL + fileName, true);
-  int pageWidth = 640;
-  int pageHeight = 480;
-  webViewImpl->resize(WebSize(pageWidth, pageHeight));
-  webViewImpl->updateAllLifecyclePhases();
-
-  Document* document =
-      toLocalFrame(webViewImpl->page()->mainFrame())->document();
-  {
-    WheelEvent* event = WheelEvent::create(
-        FloatPoint(1, 3), FloatPoint(5, 10), WheelEvent::kDomDeltaPage,
-        document->domWindow(), IntPoint(2, 6), IntPoint(10, 30),
-        PlatformEvent::CtrlKey, 0, TimeTicks(), -1 /* null plugin id */,
-        true /* hasPreciseScrollingDeltas */, Event::RailsModeHorizontal,
-        true /*cancelable*/
-#if OS(MACOSX)
-        ,
-        WheelEventPhaseBegan, WheelEventPhaseChanged
-#endif
-        );
-    WebMouseWheelEventBuilder webMouseWheel(
-        toLocalFrame(webViewImpl->page()->mainFrame())->view(),
-        document->layoutViewItem(), *event);
-    EXPECT_EQ(1, webMouseWheel.wheelTicksX);
-    EXPECT_EQ(3, webMouseWheel.wheelTicksY);
-    EXPECT_EQ(5, webMouseWheel.deltaX);
-    EXPECT_EQ(10, webMouseWheel.deltaY);
-    EXPECT_EQ(2, webMouseWheel.globalX);
-    EXPECT_EQ(6, webMouseWheel.globalY);
-    EXPECT_EQ(10, webMouseWheel.windowX);
-    EXPECT_EQ(30, webMouseWheel.windowY);
-    EXPECT_TRUE(webMouseWheel.scrollByPage);
-    EXPECT_EQ(WebInputEvent::ControlKey, webMouseWheel.modifiers);
-    EXPECT_EQ(WebInputEvent::RailsModeHorizontal, webMouseWheel.railsMode);
-    EXPECT_TRUE(webMouseWheel.hasPreciseScrollingDeltas);
-    EXPECT_EQ(WebInputEvent::Blocking, webMouseWheel.dispatchType);
-#if OS(MACOSX)
-    EXPECT_EQ(WebMouseWheelEvent::PhaseBegan, webMouseWheel.phase);
-    EXPECT_EQ(WebMouseWheelEvent::PhaseChanged, webMouseWheel.momentumPhase);
-#endif
-  }
-
-  {
-    WheelEvent* event = WheelEvent::create(
-        FloatPoint(1, 3), FloatPoint(5, 10), WheelEvent::kDomDeltaPage,
-        document->domWindow(), IntPoint(2, 6), IntPoint(10, 30),
-        PlatformEvent::CtrlKey, 0, TimeTicks(), -1 /* null plugin id */,
-        true /* hasPreciseScrollingDeltas */, Event::RailsModeHorizontal, false
-#if OS(MACOSX)
-        ,
-        WheelEventPhaseNone, WheelEventPhaseNone
-#endif
-        );
-    WebMouseWheelEventBuilder webMouseWheel(
-        toLocalFrame(webViewImpl->page()->mainFrame())->view(),
-        document->layoutViewItem(), *event);
-    EXPECT_EQ(WebInputEvent::EventNonBlocking, webMouseWheel.dispatchType);
-  }
-}
-
-TEST(WebInputEventConversionTest, PlatformWheelEventBuilder) {
-  const std::string baseURL("http://www.test8.com/");
-  const std::string fileName("fixed_layout.html");
-
-  URLTestHelpers::registerMockedURLFromBaseURL(
-      WebString::fromUTF8(baseURL.c_str()),
-      WebString::fromUTF8("fixed_layout.html"));
-  FrameTestHelpers::WebViewHelper webViewHelper;
-  WebViewImpl* webViewImpl =
-      webViewHelper.initializeAndLoad(baseURL + fileName, true);
-  int pageWidth = 640;
-  int pageHeight = 480;
-  webViewImpl->resize(WebSize(pageWidth, pageHeight));
-  webViewImpl->updateAllLifecyclePhases();
-
-  FrameView* view = toLocalFrame(webViewImpl->page()->mainFrame())->view();
-
-  {
-    WebMouseWheelEvent webMouseWheelEvent(WebInputEvent::MouseWheel,
-                                          WebInputEvent::ControlKey,
-                                          WebInputEvent::TimeStampForTesting);
-    webMouseWheelEvent.x = 0;
-    webMouseWheelEvent.y = 5;
-    webMouseWheelEvent.deltaX = 10;
-    webMouseWheelEvent.deltaY = 15;
-    webMouseWheelEvent.hasPreciseScrollingDeltas = true;
-    webMouseWheelEvent.railsMode = WebInputEvent::RailsModeHorizontal;
-    webMouseWheelEvent.phase = WebMouseWheelEvent::PhaseBegan;
-    webMouseWheelEvent.momentumPhase = WebMouseWheelEvent::PhaseChanged;
-
-    PlatformWheelEventBuilder platformWheelBuilder(view, webMouseWheelEvent);
-    EXPECT_EQ(0, platformWheelBuilder.position().x());
-    EXPECT_EQ(5, platformWheelBuilder.position().y());
-    EXPECT_EQ(10, platformWheelBuilder.deltaX());
-    EXPECT_EQ(15, platformWheelBuilder.deltaY());
-    EXPECT_EQ(PlatformEvent::CtrlKey, platformWheelBuilder.getModifiers());
-    EXPECT_TRUE(platformWheelBuilder.hasPreciseScrollingDeltas());
-    EXPECT_EQ(platformWheelBuilder.getRailsMode(),
-              PlatformEvent::RailsModeHorizontal);
-#if OS(MACOSX)
-    EXPECT_EQ(PlatformWheelEventPhaseBegan, platformWheelBuilder.phase());
-    EXPECT_EQ(PlatformWheelEventPhaseChanged,
-              platformWheelBuilder.momentumPhase());
-#endif
-  }
-
-  {
-    WebMouseWheelEvent webMouseWheelEvent(WebInputEvent::MouseWheel,
-                                          WebInputEvent::ShiftKey,
-                                          WebInputEvent::TimeStampForTesting);
-    webMouseWheelEvent.x = 5;
-    webMouseWheelEvent.y = 0;
-    webMouseWheelEvent.deltaX = 15;
-    webMouseWheelEvent.deltaY = 10;
-    webMouseWheelEvent.hasPreciseScrollingDeltas = false;
-    webMouseWheelEvent.railsMode = WebInputEvent::RailsModeFree;
-    webMouseWheelEvent.phase = WebMouseWheelEvent::PhaseNone;
-    webMouseWheelEvent.momentumPhase = WebMouseWheelEvent::PhaseNone;
-
-    PlatformWheelEventBuilder platformWheelBuilder(view, webMouseWheelEvent);
-    EXPECT_EQ(5, platformWheelBuilder.position().x());
-    EXPECT_EQ(0, platformWheelBuilder.position().y());
-    EXPECT_EQ(15, platformWheelBuilder.deltaX());
-    EXPECT_EQ(10, platformWheelBuilder.deltaY());
-    EXPECT_EQ(PlatformEvent::ShiftKey, platformWheelBuilder.getModifiers());
-    EXPECT_FALSE(platformWheelBuilder.hasPreciseScrollingDeltas());
-    EXPECT_EQ(platformWheelBuilder.getRailsMode(),
-              PlatformEvent::RailsModeFree);
-#if OS(MACOSX)
-    EXPECT_EQ(PlatformWheelEventPhaseNone, platformWheelBuilder.phase());
-    EXPECT_EQ(PlatformWheelEventPhaseNone,
-              platformWheelBuilder.momentumPhase());
-#endif
-  }
-
-  {
-    WebMouseWheelEvent webMouseWheelEvent(WebInputEvent::MouseWheel,
-                                          WebInputEvent::AltKey,
-                                          WebInputEvent::TimeStampForTesting);
-    webMouseWheelEvent.x = 5;
-    webMouseWheelEvent.y = 0;
-    webMouseWheelEvent.deltaX = 15;
-    webMouseWheelEvent.deltaY = 10;
-    webMouseWheelEvent.hasPreciseScrollingDeltas = true;
-    webMouseWheelEvent.railsMode = WebInputEvent::RailsModeVertical;
-    webMouseWheelEvent.phase = WebMouseWheelEvent::PhaseNone;
-    webMouseWheelEvent.momentumPhase = WebMouseWheelEvent::PhaseNone;
-
-    PlatformWheelEventBuilder platformWheelBuilder(view, webMouseWheelEvent);
-    EXPECT_EQ(5, platformWheelBuilder.position().x());
-    EXPECT_EQ(0, platformWheelBuilder.position().y());
-    EXPECT_EQ(15, platformWheelBuilder.deltaX());
-    EXPECT_EQ(10, platformWheelBuilder.deltaY());
-    EXPECT_EQ(PlatformEvent::AltKey, platformWheelBuilder.getModifiers());
-    EXPECT_TRUE(platformWheelBuilder.hasPreciseScrollingDeltas());
-    EXPECT_EQ(platformWheelBuilder.getRailsMode(),
-              PlatformEvent::RailsModeVertical);
-#if OS(MACOSX)
-    EXPECT_EQ(PlatformWheelEventPhaseNone, platformWheelBuilder.phase());
-    EXPECT_EQ(PlatformWheelEventPhaseNone,
-              platformWheelBuilder.momentumPhase());
-#endif
-  }
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/web/tests/WebPluginContainerTest.cpp b/third_party/WebKit/Source/web/tests/WebPluginContainerTest.cpp
index 566324a2..0d41df2 100644
--- a/third_party/WebKit/Source/web/tests/WebPluginContainerTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebPluginContainerTest.cpp
@@ -48,6 +48,7 @@
 #include "public/platform/WebClipboard.h"
 #include "public/platform/WebCompositorSupport.h"
 #include "public/platform/WebLayer.h"
+#include "public/platform/WebMouseWheelEvent.h"
 #include "public/platform/WebThread.h"
 #include "public/platform/WebURLLoaderMockFactory.h"
 #include "public/web/WebCache.h"
@@ -456,12 +457,22 @@
   WebInputEventResult handleInputEvent(const WebInputEvent& event,
                                        WebCursorInfo&) override {
     m_lastEventType = event.type;
+    if (WebInputEvent::isMouseEventType(event.type) ||
+        event.type == WebInputEvent::MouseWheel) {
+      const WebMouseEvent& mouseEvent =
+          static_cast<const WebMouseEvent&>(event);
+      m_lastMouseEventLocation = IntPoint(mouseEvent.x, mouseEvent.y);
+    }
+
     return WebInputEventResult::HandledSystem;
   }
   WebInputEvent::Type getLastInputEventType() { return m_lastEventType; }
 
+  IntPoint getLastMouseEventLocation() { return m_lastMouseEventLocation; }
+
  private:
   WebInputEvent::Type m_lastEventType;
+  IntPoint m_lastMouseEventLocation;
 };
 
 TEST_F(WebPluginContainerTest, GestureLongPressReachesPlugin) {
@@ -515,6 +526,45 @@
             testPlugin->getLastInputEventType());
 }
 
+TEST_F(WebPluginContainerTest, MouseWheelEventTranslated) {
+  URLTestHelpers::registerMockedURLFromBaseURL(
+      WebString::fromUTF8(m_baseURL.c_str()),
+      WebString::fromUTF8("plugin_container.html"));
+  CustomPluginWebFrameClient<EventTestPlugin>
+      pluginWebFrameClient;  // Must outlive webViewHelper.
+  FrameTestHelpers::WebViewHelper webViewHelper;
+  WebView* webView = webViewHelper.initializeAndLoad(
+      m_baseURL + "plugin_container.html", true, &pluginWebFrameClient);
+  DCHECK(webView);
+  webView->settings()->setPluginsEnabled(true);
+  webView->resize(WebSize(300, 300));
+  webView->updateAllLifecyclePhases();
+  runPendingTasks();
+
+  WebElement pluginContainerOneElement =
+      webView->mainFrame()->document().getElementById(
+          WebString::fromUTF8("translated-plugin"));
+  WebPlugin* plugin = static_cast<WebPluginContainerImpl*>(
+                          pluginContainerOneElement.pluginContainer())
+                          ->plugin();
+  EventTestPlugin* testPlugin = static_cast<EventTestPlugin*>(plugin);
+
+  WebMouseWheelEvent event(WebInputEvent::MouseWheel,
+                           WebInputEvent::NoModifiers,
+                           WebInputEvent::TimeStampForTesting);
+
+  WebRect rect = pluginContainerOneElement.boundsInViewport();
+  event.x = rect.x + rect.width / 2;
+  event.y = rect.y + rect.height / 2;
+
+  webView->handleInputEvent(event);
+  runPendingTasks();
+
+  EXPECT_EQ(WebInputEvent::MouseWheel, testPlugin->getLastInputEventType());
+  EXPECT_EQ(rect.width / 2, testPlugin->getLastMouseEventLocation().x());
+  EXPECT_EQ(rect.height / 2, testPlugin->getLastMouseEventLocation().y());
+}
+
 // Verify that isRectTopmost returns false when the document is detached.
 TEST_F(WebPluginContainerTest, IsRectTopmostTest) {
   URLTestHelpers::registerMockedURLFromBaseURL(
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/net/layout_test_results_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/net/layout_test_results_unittest.py
index d8f9daa..a4f5bb2 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/net/layout_test_results_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/net/layout_test_results_unittest.py
@@ -90,7 +90,6 @@
     "layout_tests_dir": "/b/build/slave/Webkit_Mac10_5/build/src/third_party/WebKit/LayoutTests",
     "version": 3,
     "num_passes": 77,
-    "has_pretty_patch": false,
     "fixable": 1220,
     "num_flaky": 0,
     "chromium_revision": "1234",
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py
index e58a5de..617be65 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py
@@ -329,7 +329,6 @@
     results['interrupted'] = initial_results.interrupted
     results['layout_tests_dir'] = port_obj.layout_tests_dir()
     results['has_wdiff'] = port_obj.wdiff_available()
-    results['has_pretty_patch'] = True
     results['pixel_tests_enabled'] = port_obj.get_option('pixel_tests')
     results['seconds_since_epoch'] = int(time.time())
     results['build_number'] = port_obj.get_option('build_number')
@@ -337,9 +336,6 @@
     if port_obj.get_option('order') == 'random':
         results['random_order_seed'] = port_obj.get_option('seed')
     results['path_delimiter'] = '/'
-    # The pretty-diff.html files should always be available.
-    # TODO(qyearsley): Change this key since PrettyPatch.rb has been removed.
-    results['has_pretty_patch'] = True
 
     # Don't do this by default since it takes >100ms.
     # It's only used for rebaselining and uploading data to the flakiness dashboard.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
index 838ff12..885b525 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -800,7 +800,9 @@
             path_in_wpt = match.group(1)
             manifest_items = self._manifest_items_for_path(path_in_wpt)
             assert manifest_items is not None
-            if len(manifest_items) != 1 or manifest_items[0]['url'][1:] != path_in_wpt:
+            # For most testharness tests, manifest_items looks like:
+            # [["/some/test/path.html", {}]]
+            if len(manifest_items) != 1 or manifest_items[0][0][1:] != path_in_wpt:
                 # TODO(tkent): foo.any.js and bar.worker.js should be accessed
                 # as foo.any.html, foo.any.worker, and bar.worker with WPTServe.
                 continue
@@ -813,16 +815,14 @@
         return json.loads(self._filesystem.read_text_file(path))
 
     def _manifest_items_for_path(self, path_in_wpt):
-        """Returns a list of a dict representing ManifestItem for the specified
-        path, or None if MANIFEST.json has no items for the specified path.
+        """Returns a manifest item for the given WPT path, or None if not found.
 
-        A ManifestItem has 'path', 'url', and optional 'timeout' fields. Also,
-        it has "references" list for reference tests. It's defined in
-        web-platform-tests/tools/manifest/item.py.
+        The format of a manifest item depends on
+        https://github.com/w3c/wpt-tools/blob/master/manifest/item.py
+        and is assumed to be a list of the format [url, extras],
+        or [url, references, extras] for reftests, or None if not found.
         """
-        # Because we generate MANIFEST.json before finishing import, all
-        # entries are in 'local_changes'.
-        items = self._wpt_manifest()['local_changes']['items']
+        items = self._wpt_manifest()['items']
         if path_in_wpt in items['manual']:
             return items['manual'][path_in_wpt]
         elif path_in_wpt in items['reftest']:
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
index 0ca0e36..76006bba 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
@@ -305,26 +305,19 @@
     @staticmethod
     def _add_manifest_to_mock_file_system(filesystem):
         filesystem.write_text_file(LAYOUT_TEST_DIR + '/imported/wpt/MANIFEST.json', json.dumps({
-            "local_changes": {
-                "items": {
-                    'testharness': {
-                        'dom/ranges/Range-attributes.html': [{
-                            'path': 'dom/ranges/Range-attributes.html',
-                            'url': '/dom/ranges/Range-attributes.html'
-                        }],
-                        'console/console-is-a-namespace.any.js': [
-                            {
-                                'path': 'console/console-is-a-namespace.any.js',
-                                'url': '/console/console-is-a-namespace.any.html'
-                            },
-                            {
-                                'path': 'console/console-is-a-namespace.any.js',
-                                'url': '/console/console-is-a-namespace.any.worker'
-                            }
-                        ]},
-                    'manual': {},
-                    'reftest': {}
-                }}}))
+            'items': {
+                'testharness': {
+                    'dom/ranges/Range-attributes.html': [
+                        ['/dom/ranges/Range-attributes.html', {}]
+                    ],
+                    'console/console-is-a-namespace.any.js': [
+                        ['/console/console-is-a-namespace.any.html', {}],
+                        ['/console/console-is-a-namespace.any.worker.html', {}],
+                    ],
+                },
+                'manual': {},
+                'reftest': {},
+            }}))
         filesystem.write_text_file(LAYOUT_TEST_DIR + '/imported/wpt/dom/ranges/Range-attributes.html', '')
         filesystem.write_text_file(LAYOUT_TEST_DIR + '/imported/wpt/console/console-is-a-namespace.any.js', '')
         filesystem.write_text_file(LAYOUT_TEST_DIR + '/imported/wpt/common/blank.html', 'foo')
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
index 2c60207f..63b43ca5 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -979,7 +979,6 @@
         full_results_text = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json')
         full_results = json.loads(full_results_text.replace("ADD_RESULTS(", "").replace(");", ""))
         self.assertEqual(full_results['has_wdiff'], False)
-        self.assertEqual(full_results['has_pretty_patch'], True)
 
     def test_unsupported_platform(self):
         stdout = StringIO.StringIO()
diff --git a/third_party/WebKit/public/platform/WebInputEvent.h b/third_party/WebKit/public/platform/WebInputEvent.h
index c3341c6..82b34ff 100644
--- a/third_party/WebKit/public/platform/WebInputEvent.h
+++ b/third_party/WebKit/public/platform/WebInputEvent.h
@@ -429,6 +429,11 @@
   WebMouseEvent()
       : WebInputEvent(sizeof(WebMouseEvent)), WebPointerProperties() {}
 
+#if INSIDE_BLINK
+  BLINK_PLATFORM_EXPORT WebFloatPoint movementInRootFrame() const;
+  BLINK_PLATFORM_EXPORT WebFloatPoint positionInRootFrame() const;
+#endif
+
  protected:
   explicit WebMouseEvent(unsigned sizeParam)
       : WebInputEvent(sizeParam), WebPointerProperties() {}
@@ -439,6 +444,7 @@
                 double timeStampSeconds)
       : WebInputEvent(sizeParam, type, modifiers, timeStampSeconds),
         WebPointerProperties() {}
+  void flattenTransformSelf();
 };
 
 // WebTouchEvent --------------------------------------------------------------
diff --git a/third_party/WebKit/public/platform/WebMouseWheelEvent.h b/third_party/WebKit/public/platform/WebMouseWheelEvent.h
index a252e638..b699d3fd 100644
--- a/third_party/WebKit/public/platform/WebMouseWheelEvent.h
+++ b/third_party/WebKit/public/platform/WebMouseWheelEvent.h
@@ -85,6 +85,17 @@
         momentumPhase(PhaseNone),
         railsMode(RailsModeFree),
         dispatchType(Blocking) {}
+
+#if INSIDE_BLINK
+  BLINK_PLATFORM_EXPORT float deltaXInRootFrame() const;
+  BLINK_PLATFORM_EXPORT float deltaYInRootFrame() const;
+
+  // Sets any scaled values to be their computed values and sets |frameScale|
+  // back to 1 and |translateX|, |translateY| back to 0.
+  BLINK_PLATFORM_EXPORT WebMouseWheelEvent flattenTransform() const;
+
+  bool isCancelable() const { return dispatchType == Blocking; }
+#endif
 };
 #pragma pack(pop)
 
diff --git a/third_party/retrolambda/README.chromium b/third_party/retrolambda/README.chromium
index 5fc43cf..9262188 100644
--- a/third_party/retrolambda/README.chromium
+++ b/third_party/retrolambda/README.chromium
@@ -16,5 +16,5 @@
 
 Source of the .jar:
 Downloaded from Maven Central. Link:
-http://search.maven.org/remotecontent?filepath=net/orfjackal/retrolambda/retrolambda/2.3.0/retrolambda-2.3.0.jar
+http://central.maven.org/maven2/net/orfjackal/retrolambda/retrolambda/2.3.0/retrolambda-2.3.0.jar
 
diff --git a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
index 0365ec1e..e2b797f 100644
--- a/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
+++ b/tools/clang/rewrite_to_chrome_style/RewriteToChromeStyle.cpp
@@ -121,6 +121,13 @@
   return true;
 }
 
+void PrintForDiagnostics(clang::raw_ostream& os,
+                         const clang::FunctionDecl& decl) {
+  decl.getLocStart().print(os, decl.getASTContext().getSourceManager());
+  os << ": ";
+  decl.getNameForDiagnostic(os, decl.getASTContext().getPrintingPolicy(), true);
+}
+
 template <typename T>
 bool MatchAllOverriddenMethods(
     const clang::CXXMethodDecl& decl,
@@ -145,13 +152,54 @@
   // one we did not rename which creates a behaviour change. So assert and
   // demand the user to fix the code first (or add the method to our
   // blacklist T_T).
-  if (override_matches || override_not_matches)
-    assert(override_matches != override_not_matches);
+  if (override_matches && override_not_matches) {
+    // blink::InternalSettings::trace method overrides
+    // 1) blink::InternalSettingsGenerated::trace
+    //    (won't be renamed because it is in generated code)
+    // 2) blink::Supplement<blink::Page>::trace
+    //    (will be renamed).
+    // It is safe to rename blink::InternalSettings::trace, because
+    // both 1 and 2 will both be renamed (#1 via manual changes of the code
+    // generator for DOM bindings and #2 via the clang tool).
+    auto internal_settings_class_decl = cxxRecordDecl(
+        hasName("InternalSettings"),
+        hasParent(namespaceDecl(hasName("blink"),
+                                hasParent(translationUnitDecl()))));
+    auto is_method_safe_to_rename = cxxMethodDecl(
+        hasName("trace"),
+        anyOf(hasParent(internal_settings_class_decl),  // in .h file
+              has(nestedNameSpecifier(specifiesType(    // in .cpp file
+                  hasDeclaration(internal_settings_class_decl))))));
+    if (IsMatching(is_method_safe_to_rename, decl, decl.getASTContext()))
+      return true;
+
+    // For previously unknown conflicts, error out and require a human to
+    // analyse the problem (rather than falling back to a potentially unsafe /
+    // code semantics changing rename).
+    llvm::errs() << "ERROR: ";
+    PrintForDiagnostics(llvm::errs(), decl);
+    llvm::errs() << " method overrides "
+                 << "some virtual methods that will be automatically renamed "
+                 << "and some that won't be renamed.";
+    llvm::errs() << "\n";
+    for (auto it = decl.begin_overridden_methods();
+         it != decl.end_overridden_methods(); ++it) {
+      if (MatchAllOverriddenMethods(**it, inner_matcher, finder, builder))
+        llvm::errs() << "Overriden method that will be renamed: ";
+      else
+        llvm::errs() << "Overriden method that will not be renamed: ";
+      PrintForDiagnostics(llvm::errs(), **it);
+      llvm::errs() << "\n";
+    }
+    llvm::errs() << "\n";
+    assert(false);
+  }
 
   // If the method overrides something that doesn't match, so the method itself
   // doesn't match.
   if (override_not_matches)
     return false;
+
   // If the method overrides something that matches, so the method ifself
   // matches.
   if (override_matches)
diff --git a/tools/licenses.py b/tools/licenses.py
index 3d37d3f..9bdc018 100755
--- a/tools/licenses.py
+++ b/tools/licenses.py
@@ -78,8 +78,6 @@
 
     # Redistribution does not require attribution in documentation.
     os.path.join('third_party','directxsdk'),
-    os.path.join('third_party','platformsdk_win2008_6_1'),
-    os.path.join('third_party','platformsdk_win7'),
 
     # For testing only, presents on some bots.
     os.path.join('isolate_deps_dir'),
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index f6a72bb..1cf29a3 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -1786,7 +1786,7 @@
     'ozone_linux': {
       'gn_args': ('ozone_auto_platforms=false ozone_platform_wayland=true '
                   'ozone_platform_x11=true ozone_platform_gbm=true '
-                  'enable_package_mash_services=true use_ash=false'),
+                  'use_ozone=true use_ash=false'),
     },
 
     'pdf_xfa': {
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 3761fc61..d8ad21e 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -107372,13 +107372,13 @@
 
 <enum name="WebsiteSettingsAction" type="int">
   <int value="0" label="Opened"/>
-  <int value="1" label="Selected Permissions tab"/>
-  <int value="2" label="Selected Connection tab"/>
-  <int value="3" label="Connection tab shown immediately"/>
+  <int value="1" label="(Deprecated) Selected Permissions tab"/>
+  <int value="2" label="(Deprecated) Selected Connection tab"/>
+  <int value="3" label="(Deprecated) Connection tab shown immediately"/>
   <int value="4" label="Cookies dialog opened"/>
   <int value="5" label="Changed permission"/>
   <int value="6" label="Certificate dialog opened"/>
-  <int value="7" label="Transparency viewer opened"/>
+  <int value="7" label="(Deprecated) Transparency viewer opened"/>
   <int value="8" label="Connection help opened"/>
   <int value="9" label="Site settings opened"/>
   <int value="10" label="Security details opened"/>
diff --git a/ui/app_list/views/search_result_container_view.cc b/ui/app_list/views/search_result_container_view.cc
index db0da6a..d51974f 100644
--- a/ui/app_list/views/search_result_container_view.cc
+++ b/ui/app_list/views/search_result_container_view.cc
@@ -33,7 +33,7 @@
   if (results_)
     results_->AddObserver(this);
 
-  DoUpdate();
+  Update();
 }
 
 void SearchResultContainerView::SetSelectedIndex(int selected_index) {
@@ -53,14 +53,12 @@
   return index >= 0 && index <= num_results() - 1;
 }
 
-void SearchResultContainerView::ScheduleUpdate() {
-  // When search results are added one by one, each addition generates an update
-  // request. Consolidates those update requests into one Update call.
-  if (!update_factory_.HasWeakPtrs()) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::Bind(&SearchResultContainerView::DoUpdate,
-                              update_factory_.GetWeakPtr()));
-  }
+void SearchResultContainerView::Update() {
+  update_factory_.InvalidateWeakPtrs();
+  num_results_ = DoUpdate();
+  Layout();
+  if (delegate_)
+    delegate_->OnSearchResultContainerResultsChanged();
 }
 
 bool SearchResultContainerView::UpdateScheduled() {
@@ -84,12 +82,14 @@
   ScheduleUpdate();
 }
 
-void SearchResultContainerView::DoUpdate() {
-  update_factory_.InvalidateWeakPtrs();
-  num_results_ = Update();
-  Layout();
-  if (delegate_)
-    delegate_->OnSearchResultContainerResultsChanged();
+void SearchResultContainerView::ScheduleUpdate() {
+  // When search results are added one by one, each addition generates an update
+  // request. Consolidates those update requests into one Update call.
+  if (!update_factory_.HasWeakPtrs()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(&SearchResultContainerView::Update,
+                              update_factory_.GetWeakPtr()));
+  }
 }
 
 }  // namespace app_list
diff --git a/ui/app_list/views/search_result_container_view.h b/ui/app_list/views/search_result_container_view.h
index 8c941a8a..e4f5fd82 100644
--- a/ui/app_list/views/search_result_container_view.h
+++ b/ui/app_list/views/search_result_container_view.h
@@ -61,9 +61,8 @@
   // container.
   virtual int GetYSize() = 0;
 
-  // Schedules an Update call using |update_factory_|. Do nothing if there is a
-  // pending call.
-  void ScheduleUpdate();
+  // Batching method that actually performs the update and updates layout.
+  void Update();
 
   // Returns whether an update is currently scheduled for this container.
   bool UpdateScheduled();
@@ -83,15 +82,16 @@
                                    bool directional_movement) = 0;
 
  private:
+  // Schedules an Update call using |update_factory_|. Do nothing if there is a
+  // pending call.
+  void ScheduleUpdate();
+
   // Updates UI with model. Returns the number of visible results.
-  virtual int Update() = 0;
+  virtual int DoUpdate() = 0;
 
   // Updates UI for a change in the selected index.
   virtual void UpdateSelectedIndex(int old_selected, int new_selected) = 0;
 
-  // Batching method that actually performs the update and updates layout.
-  void DoUpdate();
-
   Delegate* delegate_;
 
   int selected_index_;
diff --git a/ui/app_list/views/search_result_list_view.cc b/ui/app_list/views/search_result_list_view.cc
index 91cb319..dda5431 100644
--- a/ui/app_list/views/search_result_list_view.cc
+++ b/ui/app_list/views/search_result_list_view.cc
@@ -153,7 +153,7 @@
   return num_results();
 }
 
-int SearchResultListView::Update() {
+int SearchResultListView::DoUpdate() {
   std::vector<SearchResult*> display_results =
       AppListModel::FilterSearchResultsByDisplayType(
           results(),
diff --git a/ui/app_list/views/search_result_list_view.h b/ui/app_list/views/search_result_list_view.h
index c8649b5..661989e 100644
--- a/ui/app_list/views/search_result_list_view.h
+++ b/ui/app_list/views/search_result_list_view.h
@@ -64,7 +64,7 @@
   friend class test::SearchResultListViewTest;
 
   // Overridden from SearchResultContainerView:
-  int Update() override;
+  int DoUpdate() override;
   void UpdateSelectedIndex(int old_selected, int new_selected) override;
 
   // Updates the auto launch states.
diff --git a/ui/app_list/views/search_result_tile_item_list_view.cc b/ui/app_list/views/search_result_tile_item_list_view.cc
index b62f2e50..6ff0a83 100644
--- a/ui/app_list/views/search_result_tile_item_list_view.cc
+++ b/ui/app_list/views/search_result_tile_item_list_view.cc
@@ -72,7 +72,7 @@
   return num_results() ? 1 : 0;
 }
 
-int SearchResultTileItemListView::Update() {
+int SearchResultTileItemListView::DoUpdate() {
   std::vector<SearchResult*> display_results =
       AppListModel::FilterSearchResultsByDisplayType(
           results(), SearchResult::DISPLAY_TILE, kNumSearchResultTiles);
diff --git a/ui/app_list/views/search_result_tile_item_list_view.h b/ui/app_list/views/search_result_tile_item_list_view.h
index 13e1c7d..d7285a5 100644
--- a/ui/app_list/views/search_result_tile_item_list_view.h
+++ b/ui/app_list/views/search_result_tile_item_list_view.h
@@ -38,7 +38,7 @@
 
  private:
   // Overridden from SearchResultContainerView:
-  int Update() override;
+  int DoUpdate() override;
   void UpdateSelectedIndex(int old_selected, int new_selected) override;
 
   std::vector<SearchResultTileItemView*> tile_views_;
diff --git a/ui/app_list/views/start_page_view.cc b/ui/app_list/views/start_page_view.cc
index 0bac15b..fb19777 100644
--- a/ui/app_list/views/start_page_view.cc
+++ b/ui/app_list/views/start_page_view.cc
@@ -126,7 +126,7 @@
   AllAppsTileItemView* all_apps_button() { return all_apps_button_; }
 
   // Overridden from SearchResultContainerView:
-  int Update() override;
+  int DoUpdate() override;
   void UpdateSelectedIndex(int old_selected, int new_selected) override;
   void OnContainerSelected(bool from_bottom,
                            bool directional_movement) override;
@@ -171,7 +171,7 @@
   return search_result_tile_views_[index];
 }
 
-int StartPageView::StartPageTilesContainer::Update() {
+int StartPageView::StartPageTilesContainer::DoUpdate() {
   // Ignore updates and disable buttons when transitioning to a different
   // state.
   if (contents_view_->GetActiveState() != AppListModel::STATE_START) {
@@ -211,10 +211,10 @@
 void StartPageView::StartPageTilesContainer::UpdateSelectedIndex(
     int old_selected,
     int new_selected) {
-  if (old_selected >= 0)
+  if (old_selected >= 0 && old_selected < num_results())
     GetTileItemView(old_selected)->SetSelected(false);
 
-  if (new_selected >= 0)
+  if (new_selected >= 0 && new_selected < num_results())
     GetTileItemView(new_selected)->SetSelected(true);
 }
 
@@ -360,8 +360,8 @@
     custom_page_view->SetVisible(
         app_list_main_view_->ShouldShowCustomLauncherPage());
   }
-  tiles_container_->Update();
   tiles_container_->ClearSelectedIndex();
+  tiles_container_->Update();
   custom_launcher_page_background_->SetSelected(false);
 }
 
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index a9d9699..1492857 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -71,6 +71,8 @@
     "mus/capture_synchronizer.cc",
     "mus/capture_synchronizer.h",
     "mus/capture_synchronizer_delegate.h",
+    "mus/client_surface_embedder.cc",
+    "mus/client_surface_embedder.h",
     "mus/drag_drop_controller_host.h",
     "mus/drag_drop_controller_mus.cc",
     "mus/drag_drop_controller_mus.h",
@@ -114,7 +116,6 @@
     "scoped_window_targeter.h",
     "window.cc",
     "window.h",
-    "window_delegate.cc",
     "window_delegate.h",
     "window_event_dispatcher.cc",
     "window_event_dispatcher.h",
diff --git a/ui/aura/mus/DEPS b/ui/aura/mus/DEPS
index 8fe8d6d1..39280c0f 100644
--- a/ui/aura/mus/DEPS
+++ b/ui/aura/mus/DEPS
@@ -6,6 +6,7 @@
   "+cc/surfaces/surface_id_allocator.h",
   "+cc/surfaces/surface_info.h",
   "+cc/surfaces/surface_manager.h",
+  "+cc/surfaces/surface_reference_factory.h",
   "+gpu/command_buffer/client/gpu_memory_buffer_manager.h",
   "+gpu/ipc/client/gpu_channel_host.h",
   "+mojo/public/cpp/system/buffer.h",
diff --git a/ui/aura/mus/client_surface_embedder.cc b/ui/aura/mus/client_surface_embedder.cc
new file mode 100644
index 0000000..ba34a52
--- /dev/null
+++ b/ui/aura/mus/client_surface_embedder.cc
@@ -0,0 +1,90 @@
+// 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 "ui/aura/mus/client_surface_embedder.h"
+
+#include "cc/surfaces/surface_reference_factory.h"
+#include "ui/aura/mus/surface_id_handler.h"
+#include "ui/aura/window.h"
+
+namespace aura {
+namespace {
+
+// TODO(mfomitchev, samans): Remove these stub classes once the SurfaceReference
+// work is complete.
+class StubSurfaceReference : public cc::SurfaceReferenceBase {
+ public:
+  StubSurfaceReference(scoped_refptr<const cc::SurfaceReferenceFactory> factory)
+      : cc::SurfaceReferenceBase(factory) {}
+
+  ~StubSurfaceReference() override { Destroy(); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StubSurfaceReference);
+};
+
+class StubSurfaceReferenceFactory : public cc::SurfaceReferenceFactory {
+ public:
+  StubSurfaceReferenceFactory() = default;
+
+  // cc::SurfaceReferenceFactory:
+  std::unique_ptr<cc::SurfaceReferenceBase> CreateReference(
+      cc::SurfaceReferenceOwner* owner,
+      const cc::SurfaceId& surface_id) const override {
+    return base::MakeUnique<StubSurfaceReference>(make_scoped_refptr(this));
+  }
+
+ protected:
+  ~StubSurfaceReferenceFactory() override = default;
+
+ private:
+  // cc::SurfaceReferenceFactory:
+  void DestroyReference(cc::SurfaceReferenceBase* surface_ref) const override {}
+
+  DISALLOW_COPY_AND_ASSIGN(StubSurfaceReferenceFactory);
+};
+}  // namespace
+
+ClientSurfaceEmbedder::ClientSurfaceEmbedder(Window* window) : window_(window) {
+  surface_layer_ = base::MakeUnique<ui::Layer>(ui::LAYER_TEXTURED);
+  surface_layer_->SetVisible(true);
+  // The frame provided by the parent window->layer() needs to show through
+  // the surface layer.
+  surface_layer_->SetFillsBoundsOpaquely(false);
+
+  clip_layer_ = base::MakeUnique<ui::Layer>(ui::LAYER_NOT_DRAWN);
+  clip_layer_->SetFillsBoundsOpaquely(false);
+
+  clip_layer_->Add(surface_layer_.get());
+  window_->layer()->Add(clip_layer_.get());
+
+  // Window's layer may contain content from this client (the embedder), e.g.
+  // this is the case with window decorations provided by Window Manager.
+  // This content should appear underneath the content of the embedded client.
+  window_->layer()->StackAtTop(clip_layer_.get());
+
+  // We can't set this on window's layer, because that would clip the window
+  // shadow.
+  clip_layer_->SetMasksToBounds(true);
+}
+
+ClientSurfaceEmbedder::~ClientSurfaceEmbedder() = default;
+
+void ClientSurfaceEmbedder::UpdateSurface(const cc::SurfaceInfo& surface_info) {
+  // TODO(mfomitchev): Currently the frame size may not match the window size.
+  // In the future the surface id will be created by Ash (and used with the
+  // surface layer) when the window resize happens, which will ensure that the
+  // surface size matches the window size (unless a timeout occurs).
+  gfx::Size frame_size = surface_info.size_in_pixels();
+  surface_layer_->SetBounds(
+      gfx::Rect(0, 0, frame_size.width(), frame_size.height()));
+  // Clip to window bounds.
+  clip_layer_->SetBounds(
+      gfx::Rect(0, 0, window_->bounds().width(), window_->bounds().height()));
+
+  surface_layer_->SetShowSurface(
+      surface_info, make_scoped_refptr(new StubSurfaceReferenceFactory));
+}
+
+}  // namespace aura
diff --git a/ui/aura/mus/client_surface_embedder.h b/ui/aura/mus/client_surface_embedder.h
new file mode 100644
index 0000000..75b45c2
--- /dev/null
+++ b/ui/aura/mus/client_surface_embedder.h
@@ -0,0 +1,45 @@
+// 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 <memory>
+
+#include "base/macros.h"
+
+namespace cc {
+class SurfaceInfo;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace aura {
+
+class Window;
+
+// Used by WindowPortMus when it is embedding a client. Responsible for setting
+// up layers containing content from the client, parenting them to the window's
+// layer, and updating them when the client submits new surfaces.
+class ClientSurfaceEmbedder {
+ public:
+  explicit ClientSurfaceEmbedder(Window* window);
+  ~ClientSurfaceEmbedder();
+
+  // Updates the surface layer and the clip layer based on the surface info.
+  void UpdateSurface(const cc::SurfaceInfo& surface_info);
+
+ private:
+  // The window which embeds the client.
+  Window* window_;
+
+  // Contains the client's content.
+  std::unique_ptr<ui::Layer> surface_layer_;
+
+  // Used for clipping the surface layer to the window bounds.
+  std::unique_ptr<ui::Layer> clip_layer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClientSurfaceEmbedder);
+};
+
+}  // namespace aura
diff --git a/ui/aura/mus/window_port_mus.cc b/ui/aura/mus/window_port_mus.cc
index 305fcfc..da1ae15 100644
--- a/ui/aura/mus/window_port_mus.cc
+++ b/ui/aura/mus/window_port_mus.cc
@@ -7,6 +7,7 @@
 #include "base/memory/ptr_util.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/transient_window_client.h"
+#include "ui/aura/mus/client_surface_embedder.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/aura/mus/surface_id_handler.h"
 #include "ui/aura/mus/window_tree_client.h"
@@ -255,10 +256,22 @@
     }
   }
   WindowPortMus* parent = Get(window_->parent());
+  // TODO(mfomitchev): This is unused. We probably don't need this.
   if (parent && parent->surface_id_handler_) {
     parent->surface_id_handler_->OnChildWindowSurfaceChanged(window_,
                                                              surface_info);
   }
+
+  // The fact that SetSurfaceIdFromServer was called means that this window
+  // corresponds to an embedded client.
+  if (!client_surface_embedder && surface_info.id().is_valid())
+    client_surface_embedder = base::MakeUnique<ClientSurfaceEmbedder>(window_);
+
+  if (surface_info.id().is_valid())
+    client_surface_embedder->UpdateSurface(surface_info);
+  else
+    client_surface_embedder.reset();
+
   surface_info_ = surface_info;
 }
 
diff --git a/ui/aura/mus/window_port_mus.h b/ui/aura/mus/window_port_mus.h
index fd598d0f5..f687f42b 100644
--- a/ui/aura/mus/window_port_mus.h
+++ b/ui/aura/mus/window_port_mus.h
@@ -26,6 +26,7 @@
 
 namespace aura {
 
+class ClientSurfaceEmbedder;
 class PropertyConverter;
 class SurfaceIdHandler;
 class Window;
@@ -243,6 +244,9 @@
 
   Window* window_ = nullptr;
 
+  // Used when this window is embedding a client.
+  std::unique_ptr<ClientSurfaceEmbedder> client_surface_embedder;
+
   ServerChangeIdType next_server_change_id_ = 0;
   ServerChanges server_changes_;
 
diff --git a/ui/aura/window_delegate.cc b/ui/aura/window_delegate.cc
deleted file mode 100644
index c3503d8..0000000
--- a/ui/aura/window_delegate.cc
+++ /dev/null
@@ -1,11 +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 "ui/aura/window_delegate.h"
-
-namespace aura {
-
-void WindowDelegate::OnRequestClose() {}
-
-}  // namespace aura
diff --git a/ui/aura/window_delegate.h b/ui/aura/window_delegate.h
index 434dcad5..644c0ce 100644
--- a/ui/aura/window_delegate.h
+++ b/ui/aura/window_delegate.h
@@ -94,9 +94,6 @@
   // above returns true.
   virtual void GetHitTestMask(gfx::Path* mask) const = 0;
 
-  // Sent when the server requests the window to close.
-  virtual void OnRequestClose();
-
  protected:
   ~WindowDelegate() override {}
 };
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index 8f674a6..9150bf5 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -98,7 +98,6 @@
   enum DrawFunction {
     STRING_WITH_HALO,
     STRING_FADED,
-    STRING_WITH_SHADOWS
   };
 
   DrawStringLayerDelegate(
@@ -127,14 +126,6 @@
         recorder.canvas()->DrawFadedString(
             text, font_list_, SK_ColorRED, bounds, 0);
         break;
-      case STRING_WITH_SHADOWS: {
-        gfx::ShadowValues shadows;
-        shadows.push_back(
-            gfx::ShadowValue(gfx::Vector2d(2, 2), 2, SK_ColorRED));
-        recorder.canvas()->DrawStringRectWithShadows(
-            text, font_list_, SK_ColorRED, bounds, 0, 0, shadows);
-        break;
-      }
       default:
         NOTREACHED();
     }
@@ -1526,41 +1517,6 @@
                                  large_error_allowed,
                                  small_error_allowed)));
 }
-
-TEST_F(LayerWithRealCompositorTest, CanvasDrawStringRectWithShadows) {
-  gfx::Size size(50, 50);
-  GetCompositor()->SetScaleAndSize(1.0f, size);
-  DrawStringLayerDelegate delegate(
-      SK_ColorBLUE, SK_ColorWHITE,
-      DrawStringLayerDelegate::STRING_WITH_SHADOWS,
-      size);
-  std::unique_ptr<Layer> layer(
-      CreateDrawStringLayer(gfx::Rect(size), &delegate));
-  DrawTree(layer.get());
-
-  SkBitmap bitmap;
-  ReadPixels(&bitmap);
-  ASSERT_FALSE(bitmap.empty());
-
-  base::FilePath ref_img =
-      test_data_directory().AppendASCII("string_with_shadows.png");
-  // WritePNGFile(bitmap, ref_img, true);
-
-  float percentage_pixels_large_error = 7.4f;  // 185px / (50*50)
-  float percentage_pixels_small_error = 0.0f;
-  float average_error_allowed_in_bad_pixels = 60.f;
-  int large_error_allowed = 246;
-  int small_error_allowed = 0;
-
-  EXPECT_TRUE(MatchesPNGFile(bitmap, ref_img,
-                             cc::FuzzyPixelComparator(
-                                 true,
-                                 percentage_pixels_large_error,
-                                 percentage_pixels_small_error,
-                                 average_error_allowed_in_bad_pixels,
-                                 large_error_allowed,
-                                 small_error_allowed)));
-}
 #endif  // defined(OS_WIN)
 
 // Opacity is rendered correctly.
diff --git a/ui/gfx/canvas.cc b/ui/gfx/canvas.cc
index 9e82c7f..5f2bff97 100644
--- a/ui/gfx/canvas.cc
+++ b/ui/gfx/canvas.cc
@@ -463,15 +463,6 @@
                           DefaultCanvasTextAlignment());
 }
 
-void Canvas::DrawStringRectWithFlags(const base::string16& text,
-                                     const FontList& font_list,
-                                     SkColor color,
-                                     const Rect& display_rect,
-                                     int flags) {
-  DrawStringRectWithShadows(text, font_list, color, display_rect, 0, flags,
-                            ShadowValues());
-}
-
 void Canvas::TileImageInt(const ImageSkia& image,
                           int x,
                           int y,
diff --git a/ui/gfx/canvas.h b/ui/gfx/canvas.h
index 00f5b91..adf1286 100644
--- a/ui/gfx/canvas.h
+++ b/ui/gfx/canvas.h
@@ -16,7 +16,6 @@
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/native_widget_types.h"
-#include "ui/gfx/shadow_value.h"
 #include "ui/gfx/text_constants.h"
 
 namespace gfx {
@@ -28,6 +27,7 @@
 class PointF;
 class Size;
 class Transform;
+class Vector2d;
 
 // Canvas is a SkCanvas wrapper that provides a number of methods for
 // common operations used throughout an application built using ui/gfx.
@@ -407,17 +407,6 @@
                                const Rect& display_rect,
                                int flags);
 
-  // Similar to above DrawStringRect method but with text shadows support.
-  // Currently it's only implemented for canvas skia. Specifying a 0 line_height
-  // will cause the default height to be used.
-  void DrawStringRectWithShadows(const base::string16& text,
-                                 const FontList& font_list,
-                                 SkColor color,
-                                 const Rect& text_bounds,
-                                 int line_height,
-                                 int flags,
-                                 const ShadowValues& shadows);
-
   // Draws a dotted gray rectangle used for focus purposes.
   // DEPRECATED in favor of the RectF version below.
   // TODO(funkysidd): Remove this (http://crbug.com/553726)
diff --git a/ui/gfx/canvas_notimplemented.cc b/ui/gfx/canvas_notimplemented.cc
index f31de5a..dc661b5 100644
--- a/ui/gfx/canvas_notimplemented.cc
+++ b/ui/gfx/canvas_notimplemented.cc
@@ -27,13 +27,11 @@
   NOTIMPLEMENTED();
 }
 
-void Canvas::DrawStringRectWithShadows(const base::string16& text,
-                                       const FontList& font_list,
-                                       SkColor color,
-                                       const Rect& text_bounds,
-                                       int line_height,
-                                       int flags,
-                                       const ShadowValues& shadows) {
+void Canvas::DrawStringRectWithFlags(const base::string16& text,
+                                     const FontList& font_list,
+                                     SkColor color,
+                                     const Rect& text_bounds,
+                                     int flags) {
   NOTIMPLEMENTED();
 }
 
diff --git a/ui/gfx/canvas_skia.cc b/ui/gfx/canvas_skia.cc
index 7aa114b1..3fd8f36 100644
--- a/ui/gfx/canvas_skia.cc
+++ b/ui/gfx/canvas_skia.cc
@@ -143,26 +143,20 @@
   }
 }
 
-void Canvas::DrawStringRectWithShadows(const base::string16& text,
-                                       const FontList& font_list,
-                                       SkColor color,
-                                       const Rect& text_bounds,
-                                       int line_height,
-                                       int flags,
-                                       const ShadowValues& shadows) {
+void Canvas::DrawStringRectWithFlags(const base::string16& text,
+                                     const FontList& font_list,
+                                     SkColor color,
+                                     const Rect& text_bounds,
+                                     int flags) {
   if (!IntersectsClipRect(RectToSkRect(text_bounds)))
     return;
 
-  Rect clip_rect(text_bounds);
-  clip_rect.Inset(ShadowValue::GetMargin(shadows));
-
   canvas_->save();
-  ClipRect(clip_rect);
+  ClipRect(text_bounds);
 
   Rect rect(text_bounds);
 
   std::unique_ptr<RenderText> render_text(RenderText::CreateInstance());
-  render_text->set_shadows(shadows);
   render_text->set_halo_effect(!!(flags & HALO_EFFECT));
 
   if (flags & MULTI_LINE) {
@@ -182,10 +176,7 @@
       UpdateRenderText(rect, strings[i], font_list, flags, color,
                        render_text.get());
       int line_padding = 0;
-      if (line_height > 0)
-        line_padding = line_height - render_text->GetStringSize().height();
-      else
-        line_height = render_text->GetStringSize().height();
+      const int line_height = render_text->GetStringSize().height();
 
       // TODO(msw|asvitkine): Center Windows multi-line text: crbug.com/107357
 #if !defined(OS_WIN)
diff --git a/ui/gfx/test/data/compositor/string_with_shadows.png b/ui/gfx/test/data/compositor/string_with_shadows.png
deleted file mode 100644
index e9b8926..0000000
--- a/ui/gfx/test/data/compositor/string_with_shadows.png
+++ /dev/null
Binary files differ
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index af86e79..24eaae3 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -17,7 +17,6 @@
 #include "base/win/scoped_select_object.h"
 #include "base/win/win_util.h"
 #include "base/win/windows_version.h"
-#include "skia/ext/bitmap_platform_device.h"
 #include "skia/ext/platform_canvas.h"
 #include "skia/ext/skia_utils_win.h"
 #include "third_party/skia/include/core/SkCanvas.h"
diff --git a/ui/views/bubble/bubble_border.cc b/ui/views/bubble/bubble_border.cc
index 0746d87..f932250 100644
--- a/ui/views/bubble/bubble_border.cc
+++ b/ui/views/bubble/bubble_border.cc
@@ -5,6 +5,7 @@
 #include "ui/views/bubble/bubble_border.h"
 
 #include <algorithm>
+#include <vector>
 
 #include "base/logging.h"
 #include "third_party/skia/include/core/SkDrawLooper.h"
@@ -16,6 +17,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/path.h"
 #include "ui/gfx/scoped_canvas.h"
+#include "ui/gfx/shadow_value.h"
 #include "ui/gfx/skia_util.h"
 #include "ui/resources/grit/ui_resources.h"
 #include "ui/views/painter.h"
diff --git a/ui/views/controls/button/toggle_button.cc b/ui/views/controls/button/toggle_button.cc
index c0ad3922..37c2ce1 100644
--- a/ui/views/controls/button/toggle_button.cc
+++ b/ui/views/controls/button/toggle_button.cc
@@ -11,6 +11,7 @@
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/shadow_value.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_ripple.h"
 #include "ui/views/border.h"