diff --git a/DEPS b/DEPS
index 9275223..b9bce90 100644
--- a/DEPS
+++ b/DEPS
@@ -129,7 +129,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': '12cf258193dc2b92c19fafefde28265f1201f578',
+  'skia_revision': 'e1c5ea6779f431023c54e801c662723b3547381a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -141,11 +141,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '065f8dc35f9dffead39ced85a0ab1ba447ccf843',
+  'angle_revision': '639729c3e65e7d4c127bebf5a288ad31918dba8e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'db4f3dfd19601c7c544f2b1eb2e98706a33f7902',
+  'swiftshader_revision': '64b761a8af9b7f15895de979e242d9d74f51321d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd7ba750e8dd8eb4df11ae6a5bd01e62de590efae',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0d4500b93834267bf2ba73404c4c944015603b86',
       'condition': 'checkout_linux',
   },
 
@@ -1183,7 +1183,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'e7d286efc8764138402b33c0f476025aded7c3fc',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '27acfdb0261ea24940109aeb4444f55b94eae720',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1354,7 +1354,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '3f6583d3fee4ab71866ade794504a20eb6f63f88',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '79e9f4b9c128963eef2c7031dd9311f86fca5535',
+    Var('webrtc_git') + '/src.git' + '@' + '8607f843a71d0718e2a753a027dc703c5de03f69',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1395,7 +1395,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2221eb4885adfdeb675107859a225d2dd56e3a52',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@93758446cf84566ab8617abea5a9ab70e8305a19',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/app_list/views/suggestion_chip_container_view.cc b/ash/app_list/views/suggestion_chip_container_view.cc
index ef256ba3..c17a55e 100644
--- a/ash/app_list/views/suggestion_chip_container_view.cc
+++ b/ash/app_list/views/suggestion_chip_container_view.cc
@@ -65,13 +65,16 @@
   if (IgnoreUpdateAndLayout())
     return num_results();
 
-  auto exclude_reinstall_filter = [](const SearchResult& r) -> bool {
+  // Need to filter out kArcAppShortcut since it will be confusing to users
+  // if shortcuts are displayed as suggestion chips.
+  auto filter_reinstall_and_shortcut = [](const SearchResult& r) -> bool {
     return r.display_type() == ash::SearchResultDisplayType::kRecommendation &&
-           r.result_type() != ash::SearchResultType::kPlayStoreReinstallApp;
+           r.result_type() != ash::SearchResultType::kPlayStoreReinstallApp &&
+           r.result_type() != ash::SearchResultType::kArcAppShortcut;
   };
   std::vector<SearchResult*> display_results =
       SearchModel::FilterSearchResultsByFunction(
-          results(), base::BindRepeating(exclude_reinstall_filter),
+          results(), base::BindRepeating(filter_reinstall_and_shortcut),
           AppListConfig::instance().num_start_page_tiles());
 
   // Update search results here, but wait until layout to add them as child
diff --git a/ash/system/unified/top_shortcuts_view.cc b/ash/system/unified/top_shortcuts_view.cc
index eba1261..9603838 100644
--- a/ash/system/unified/top_shortcuts_view.cc
+++ b/ash/system/unified/top_shortcuts_view.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/unified/top_shortcuts_view.h"
 
+#include <numeric>
+
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/public/cpp/ash_view_ids.h"
 #include "ash/resources/vector_icons/vector_icons.h"
@@ -18,6 +20,7 @@
 #include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/user_chooser_view.h"
+#include "base/numerics/ranges.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
@@ -62,73 +65,52 @@
 // Buttons are equally spaced by the default value, but the gap will be
 // narrowed evenly when the parent view is not large enough.
 void TopShortcutButtonContainer::Layout() {
-  gfx::Rect child_area(GetContentsBounds());
+  const gfx::Rect child_area = GetContentsBounds();
 
-  int total_horizontal_size = 0;
-  int num_visible = 0;
-  for (int i = 0; i < child_count(); i++) {
-    const views::View* child = child_at(i);
-    if (!child->visible())
-      continue;
-    int child_horizontal_size = child->GetPreferredSize().width();
-    if (child_horizontal_size == 0)
-      continue;
-    total_horizontal_size += child_horizontal_size;
-    num_visible++;
-  }
+  views::View::Views visible_children;
+  std::copy_if(children().cbegin(), children().cend(),
+               std::back_inserter(visible_children), [](const auto* v) {
+                 return v->visible() && (v->GetPreferredSize().width() > 0);
+               });
+  if (visible_children.empty())
+    return;
+
+  const int visible_child_width =
+      std::accumulate(visible_children.cbegin(), visible_children.cend(), 0,
+                      [](int width, const auto* v) {
+                        return width + v->GetPreferredSize().width();
+                      });
 
   int spacing = 0;
-  if (num_visible > 1) {
-    spacing = std::max(kUnifiedTopShortcutButtonMinSpacing,
-                         std::min(kUnifiedTopShortcutButtonDefaultSpacing,
-                                  (child_area.width() - total_horizontal_size) /
-                                      (num_visible - 1)));
+  if (visible_children.size() > 1) {
+    spacing = (child_area.width() - visible_child_width) /
+              (int{visible_children.size()} - 1);
+    spacing = base::ClampToRange(spacing, kUnifiedTopShortcutButtonMinSpacing,
+                                 kUnifiedTopShortcutButtonDefaultSpacing);
   }
 
-  int sign_out_button_width = 0;
-  if (sign_out_button_ && sign_out_button_->visible()) {
-    // resize the sign-out button
-    int remainder = child_area.width() -
-                    (num_visible - 1) * kUnifiedTopShortcutButtonMinSpacing -
-                    total_horizontal_size +
-                    sign_out_button_->GetPreferredSize().width();
-    sign_out_button_width = std::max(
-        0, std::min(sign_out_button_->GetPreferredSize().width(), remainder));
-  }
+  int x = child_area.x();
+  int y = child_area.y() + kUnifiedTopShortcutContainerTopPadding +
+          kUnifiedCircularButtonFocusPadding.bottom();
+  for (auto* child : visible_children) {
+    int child_y = y;
+    int width = child->GetPreferredSize().width();
+    if (child == user_avatar_button_) {
+      x -= kUnifiedCircularButtonFocusPadding.left();
+      child_y -= kUnifiedCircularButtonFocusPadding.bottom();
+    } else if (child == sign_out_button_) {
+      // When there's not enough space, shrink the sign-out button.
+      const int remainder = child_area.width() -
+                            (int{visible_children.size()} - 1) * spacing -
+                            (visible_child_width - width);
+      width = base::ClampToRange(width, 0, remainder);
+    }
 
-  int horizontal_position = child_area.x();
+    child->SetBounds(x, child_y, width, child->GetHeightForWidth(width));
+    x += width + spacing;
 
-  if (user_avatar_button_ && user_avatar_button_->visible()) {
-    int vertical_position =
-        child_area.y() + kUnifiedTopShortcutContainerTopPadding;
-    horizontal_position -= kUnifiedCircularButtonFocusPadding.left();
-
-    gfx::Size size = user_avatar_button_->GetPreferredSize();
-    gfx::Rect user_avatar_bounds(horizontal_position, vertical_position,
-                                 size.width(), size.height());
-    user_avatar_button_->SetBoundsRect(user_avatar_bounds);
-
-    horizontal_position += user_avatar_bounds.size().width() + spacing -
-                           kUnifiedCircularButtonFocusPadding.right();
-  }
-
-  int vertical_position = child_area.y() +
-                          kUnifiedTopShortcutContainerTopPadding +
-                          kUnifiedCircularButtonFocusPadding.bottom();
-  for (int i = 1; i < child_count(); i++) {
-    views::View* child = child_at(i);
-    if (!child->visible())
-      continue;
-    gfx::Rect bounds(child_area);
-    bounds.set_x(horizontal_position);
-    bounds.set_y(vertical_position);
-
-    int width = (child == sign_out_button_) ? sign_out_button_width
-                                            : child->GetPreferredSize().width();
-    bounds.set_width(width);
-    bounds.set_height(child->GetHeightForWidth(width));
-    child->SetBoundsRect(bounds);
-    horizontal_position += width + spacing;
+    if (child == user_avatar_button_)
+      x -= kUnifiedCircularButtonFocusPadding.right();
   }
 }
 
diff --git a/base/memory/platform_shared_memory_region_android.cc b/base/memory/platform_shared_memory_region_android.cc
index 77b1051..8869847 100644
--- a/base/memory/platform_shared_memory_region_android.cc
+++ b/base/memory/platform_shared_memory_region_android.cc
@@ -25,7 +25,8 @@
 static int GetAshmemRegionProtectionMask(int fd) {
   int prot = ashmem_get_prot_region(fd);
   if (prot < 0) {
-    DPLOG(ERROR) << "ashmem_get_prot_region failed";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    PLOG(ERROR) << "ashmem_get_prot_region failed";
     return -1;
   }
   return prot;
@@ -189,9 +190,10 @@
   bool expected_read_only = mode == Mode::kReadOnly;
 
   if (is_read_only != expected_read_only) {
-    DLOG(ERROR) << "Ashmem region has a wrong protection mask: it is"
-                << (is_read_only ? " " : " not ") << "read-only but it should"
-                << (expected_read_only ? " " : " not ") << "be";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    LOG(ERROR) << "Ashmem region has a wrong protection mask: it is"
+               << (is_read_only ? " " : " not ") << "read-only but it should"
+               << (expected_read_only ? " " : " not ") << "be";
     return false;
   }
 
diff --git a/base/memory/platform_shared_memory_region_fuchsia.cc b/base/memory/platform_shared_memory_region_fuchsia.cc
index 049d8cf2a..4ca16e3 100644
--- a/base/memory/platform_shared_memory_region_fuchsia.cc
+++ b/base/memory/platform_shared_memory_region_fuchsia.cc
@@ -168,7 +168,8 @@
   zx_status_t status = handle->get_info(ZX_INFO_HANDLE_BASIC, &basic,
                                         sizeof(basic), nullptr, nullptr);
   if (status != ZX_OK) {
-    ZX_DLOG(ERROR, status) << "zx_object_get_info";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    ZX_LOG(ERROR, status) << "zx_object_get_info";
     return false;
   }
 
@@ -176,9 +177,10 @@
   bool expected_read_only = mode == Mode::kReadOnly;
 
   if (is_read_only != expected_read_only) {
-    DLOG(ERROR) << "VMO object has wrong access rights: it is"
-                << (is_read_only ? " " : " not ") << "read-only but it should"
-                << (expected_read_only ? " " : " not ") << "be";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    LOG(ERROR) << "VMO object has wrong access rights: it is"
+               << (is_read_only ? " " : " not ") << "read-only but it should"
+               << (expected_read_only ? " " : " not ") << "be";
     return false;
   }
 
diff --git a/base/memory/platform_shared_memory_region_mac.cc b/base/memory/platform_shared_memory_region_mac.cc
index 95a4f30..57eb104e 100644
--- a/base/memory/platform_shared_memory_region_mac.cc
+++ b/base/memory/platform_shared_memory_region_mac.cc
@@ -223,10 +223,11 @@
   if (kr == KERN_SUCCESS) {
     kern_return_t kr_deallocate =
         mach_vm_deallocate(mach_task_self(), temp_addr, size);
-    MACH_DLOG_IF(ERROR, kr_deallocate != KERN_SUCCESS, kr_deallocate)
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    MACH_LOG_IF(ERROR, kr_deallocate != KERN_SUCCESS, kr_deallocate)
         << "mach_vm_deallocate";
   } else if (kr != KERN_INVALID_RIGHT) {
-    MACH_DLOG(ERROR, kr) << "mach_vm_map";
+    MACH_LOG(ERROR, kr) << "mach_vm_map";
     return false;
   }
 
@@ -234,9 +235,10 @@
   bool expected_read_only = mode == Mode::kReadOnly;
 
   if (is_read_only != expected_read_only) {
-    DLOG(ERROR) << "VM region has a wrong protection mask: it is"
-                << (is_read_only ? " " : " not ") << "read-only but it should"
-                << (expected_read_only ? " " : " not ") << "be";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    LOG(ERROR) << "VM region has a wrong protection mask: it is"
+               << (is_read_only ? " " : " not ") << "read-only but it should"
+               << (expected_read_only ? " " : " not ") << "be";
     return false;
   }
 
diff --git a/base/memory/platform_shared_memory_region_posix.cc b/base/memory/platform_shared_memory_region_posix.cc
index d6b46e41..a17b4c1 100644
--- a/base/memory/platform_shared_memory_region_posix.cc
+++ b/base/memory/platform_shared_memory_region_posix.cc
@@ -34,14 +34,16 @@
 bool CheckFDAccessMode(int fd, int expected_mode) {
   int fd_status = fcntl(fd, F_GETFL);
   if (fd_status == -1) {
-    DPLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    PLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed";
     return false;
   }
 
   int mode = fd_status & O_ACCMODE;
   if (mode != expected_mode) {
-    DLOG(ERROR) << "Descriptor access mode (" << mode
-                << ") differs from expected (" << expected_mode << ")";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    LOG(ERROR) << "Descriptor access mode (" << mode
+               << ") differs from expected (" << expected_mode << ")";
     return false;
   }
 
@@ -301,7 +303,8 @@
 
   // The second descriptor must be invalid in kReadOnly and kUnsafe modes.
   if (handle.readonly_fd != -1) {
-    DLOG(ERROR) << "The second descriptor must be invalid";
+    // TODO(crbug.com/838365): convert to DLOG when bug fixed.
+    LOG(ERROR) << "The second descriptor must be invalid";
     return false;
   }
 
diff --git a/base/task/sequence_manager/real_time_domain.cc b/base/task/sequence_manager/real_time_domain.cc
index c27b93d..64331298 100644
--- a/base/task/sequence_manager/real_time_domain.cc
+++ b/base/task/sequence_manager/real_time_domain.cc
@@ -4,7 +4,7 @@
 
 #include "base/task/sequence_manager/real_time_domain.h"
 
-#include "base/task/sequence_manager/sequence_manager.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
 
 namespace base {
 namespace sequence_manager {
@@ -14,12 +14,18 @@
 
 RealTimeDomain::~RealTimeDomain() = default;
 
+void RealTimeDomain::OnRegisterWithSequenceManager(
+    SequenceManagerImpl* sequence_manager) {
+  TimeDomain::OnRegisterWithSequenceManager(sequence_manager);
+  tick_clock_ = sequence_manager->GetTickClock();
+}
+
 LazyNow RealTimeDomain::CreateLazyNow() const {
-  return LazyNow(sequence_manager()->GetTickClock());
+  return LazyNow(tick_clock_);
 }
 
 TimeTicks RealTimeDomain::Now() const {
-  return sequence_manager()->NowTicks();
+  return tick_clock_->NowTicks();
 }
 
 Optional<TimeDelta> RealTimeDomain::DelayTillNextTask(LazyNow* lazy_now) {
diff --git a/base/task/sequence_manager/real_time_domain.h b/base/task/sequence_manager/real_time_domain.h
index 4f95bf57..2798002 100644
--- a/base/task/sequence_manager/real_time_domain.h
+++ b/base/task/sequence_manager/real_time_domain.h
@@ -25,9 +25,13 @@
   bool MaybeFastForwardToNextTask(bool quit_when_idle_requested) override;
 
  protected:
+  void OnRegisterWithSequenceManager(
+      SequenceManagerImpl* sequence_manager) override;
   const char* GetName() const override;
 
  private:
+  const TickClock* tick_clock_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(RealTimeDomain);
 };
 
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index 03c5cce..f9db542 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "base/cancelable_callback.h"
 #include "base/location.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/scoped_refptr.h"
@@ -4648,6 +4649,39 @@
   RunLoop().RunUntilIdle();
 }
 
+TEST_P(SequenceManagerTest, ReclaimMemoryRemovesCorrectQueueFromSet) {
+  auto queue1 = CreateTaskQueue();
+  auto queue2 = CreateTaskQueue();
+  auto queue3 = CreateTaskQueue();
+  auto queue4 = CreateTaskQueue();
+
+  std::vector<int> order;
+
+  CancelableClosure cancelable_closure1(
+      BindLambdaForTesting([&]() { order.push_back(10); }));
+  CancelableClosure cancelable_closure2(
+      BindLambdaForTesting([&]() { order.push_back(11); }));
+  queue1->task_runner()->PostTask(FROM_HERE, BindLambdaForTesting([&]() {
+                                    order.push_back(1);
+                                    cancelable_closure1.Cancel();
+                                    cancelable_closure2.Cancel();
+                                    // This should remove |queue4| from the work
+                                    // queue set,
+                                    sequence_manager()->ReclaimMemory();
+                                  }));
+  queue2->task_runner()->PostTask(
+      FROM_HERE, BindLambdaForTesting([&]() { order.push_back(2); }));
+  queue3->task_runner()->PostTask(
+      FROM_HERE, BindLambdaForTesting([&]() { order.push_back(3); }));
+  queue4->task_runner()->PostTask(FROM_HERE, cancelable_closure1.callback());
+  queue4->task_runner()->PostTask(FROM_HERE, cancelable_closure2.callback());
+
+  RunLoop().RunUntilIdle();
+
+  // Make sure ReclaimMemory didn't prevent the task from |queue2| from running.
+  EXPECT_THAT(order, ElementsAre(1, 2, 3));
+}
+
 }  // namespace sequence_manager_impl_unittest
 }  // namespace internal
 }  // namespace sequence_manager
diff --git a/base/task/sequence_manager/time_domain.h b/base/task/sequence_manager/time_domain.h
index 4ffdf4d..22506672 100644
--- a/base/task/sequence_manager/time_domain.h
+++ b/base/task/sequence_manager/time_domain.h
@@ -93,16 +93,17 @@
 
   virtual const char* GetName() const = 0;
 
+  // Called when the TimeDomain is registered. |sequence_manager| is expected to
+  // be valid for the duration of TimeDomain's existence.
+  // TODO(scheduler-dev): Pass SequenceManager in the constructor.
+  virtual void OnRegisterWithSequenceManager(
+      internal::SequenceManagerImpl* sequence_manager);
+
  private:
   friend class internal::TaskQueueImpl;
   friend class internal::SequenceManagerImpl;
   friend class TestTimeDomain;
 
-  // Called when the TimeDomain is registered.
-  // TODO(kraynov): Pass SequenceManager in the constructor.
-  void OnRegisterWithSequenceManager(
-      internal::SequenceManagerImpl* sequence_manager);
-
   // Schedule TaskQueue to wake up at certain time, repeating calls with
   // the same |queue| invalidate previous requests.
   // Nullopt |wake_up| cancels a previously set wake up for |queue|.
diff --git a/base/task/sequence_manager/work_queue.cc b/base/task/sequence_manager/work_queue.cc
index b8c87b4..0d9df4a 100644
--- a/base/task/sequence_manager/work_queue.cc
+++ b/base/task/sequence_manager/work_queue.cc
@@ -182,6 +182,7 @@
     tasks_.MaybeShrinkQueue();
   }
 
+  DCHECK(work_queue_sets_);
 #if DCHECK_IS_ON()
   // If diagnostics are on it's possible task queues are being selected at
   // random so we can't use the (slightly) more efficient OnPopMinQueueInSet.
@@ -249,10 +250,11 @@
 
 bool WorkQueue::InsertFence(EnqueueOrder fence) {
   bool was_blocked_by_fence = InsertFenceImpl(fence);
+  if (!work_queue_sets_)
+    return false;
 
   // Moving the fence forward may unblock some tasks.
-  if (work_queue_sets_ && !tasks_.empty() && was_blocked_by_fence &&
-      !BlockedByFence()) {
+  if (!tasks_.empty() && was_blocked_by_fence && !BlockedByFence()) {
     work_queue_sets_->OnTaskPushedToEmptyQueue(this);
     return true;
   }
@@ -294,6 +296,7 @@
 
   if (work_queue_sets_ && heap_handle().IsValid())
     work_queue_sets_->OnQueuesFrontTaskChanged(this);
+  DCHECK(!heap_handle_.IsValid());
 }
 
 void WorkQueue::PopTaskForTesting() {
diff --git a/base/task/sequence_manager/work_queue.h b/base/task/sequence_manager/work_queue.h
index 469be50..849d48c 100644
--- a/base/task/sequence_manager/work_queue.h
+++ b/base/task/sequence_manager/work_queue.h
@@ -168,6 +168,10 @@
   WorkQueueSets* work_queue_sets_ = nullptr;  // NOT OWNED.
   TaskQueueImpl* const task_queue_;           // NOT OWNED.
   size_t work_queue_set_index_ = 0;
+
+  // Iff the queue isn't empty (or appearing to be empty due to a fence) then
+  // |heap_handle_| will be valid and correspond to this queue's location within
+  // an IntrusiveHeap inside the WorkQueueSet.
   base::internal::HeapHandle heap_handle_;
   const char* const name_;
   EnqueueOrder fence_;
diff --git a/base/task/sequence_manager/work_queue_sets.cc b/base/task/sequence_manager/work_queue_sets.cc
index c51e781..c2f9886 100644
--- a/base/task/sequence_manager/work_queue_sets.cc
+++ b/base/task/sequence_manager/work_queue_sets.cc
@@ -25,6 +25,7 @@
 void WorkQueueSets::AddQueue(WorkQueue* work_queue, size_t set_index) {
   DCHECK(!work_queue->work_queue_sets());
   DCHECK_LT(set_index, work_queue_heaps_.size());
+  DCHECK(!work_queue->heap_handle().IsValid());
   EnqueueOrder enqueue_order;
   bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
   work_queue->AssignToWorkQueueSets(this);
@@ -40,14 +41,14 @@
 void WorkQueueSets::RemoveQueue(WorkQueue* work_queue) {
   DCHECK_EQ(this, work_queue->work_queue_sets());
   work_queue->AssignToWorkQueueSets(nullptr);
-  base::internal::HeapHandle heap_handle = work_queue->heap_handle();
-  if (!heap_handle.IsValid())
+  if (!work_queue->heap_handle().IsValid())
     return;
   size_t set_index = work_queue->work_queue_set_index();
   DCHECK_LT(set_index, work_queue_heaps_.size());
-  work_queue_heaps_[set_index].erase(heap_handle);
+  work_queue_heaps_[set_index].erase(work_queue->heap_handle());
   if (work_queue_heaps_[set_index].empty())
     observer_->WorkQueueSetBecameEmpty(set_index);
+  DCHECK(!work_queue->heap_handle().IsValid());
 }
 
 void WorkQueueSets::ChangeSetIndex(WorkQueue* work_queue, size_t set_index) {
@@ -59,6 +60,7 @@
   DCHECK_LT(old_set, work_queue_heaps_.size());
   DCHECK_NE(old_set, set_index);
   work_queue->AssignSetIndex(set_index);
+  DCHECK_EQ(has_enqueue_order, work_queue->heap_handle().IsValid());
   if (!has_enqueue_order)
     return;
   work_queue_heaps_[old_set].erase(work_queue->heap_handle());
@@ -83,9 +85,8 @@
                                            {enqueue_order, work_queue});
   } else {
     // O(log n)
-    work_queue_heaps_[set_index].Pop();
-    DCHECK(work_queue_heaps_[set_index].empty() ||
-           work_queue_heaps_[set_index].Min().value != work_queue);
+    work_queue_heaps_[set_index].erase(work_queue->heap_handle());
+    DCHECK(!work_queue->heap_handle().IsValid());
     if (work_queue_heaps_[set_index].empty())
       observer_->WorkQueueSetBecameEmpty(set_index);
   }
@@ -125,6 +126,7 @@
   } else {
     // O(log n)
     work_queue_heaps_[set_index].Pop();
+    DCHECK(!work_queue->heap_handle().IsValid());
     DCHECK(work_queue_heaps_[set_index].empty() ||
            work_queue_heaps_[set_index].Min().value != work_queue);
     if (work_queue_heaps_[set_index].empty()) {
@@ -162,6 +164,7 @@
   if (work_queue_heaps_[set_index].empty())
     return nullptr;
   const OldestTaskEnqueueOrder& oldest = work_queue_heaps_[set_index].Min();
+  DCHECK(oldest.value->heap_handle().IsValid());
   *out_enqueue_order = oldest.key;
   EnqueueOrder enqueue_order;
   DCHECK(oldest.value->GetFrontTaskEnqueueOrder(&enqueue_order) &&
diff --git a/base/task/sequence_manager/work_queue_sets_unittest.cc b/base/task/sequence_manager/work_queue_sets_unittest.cc
index 2332b26..30f3bb8f 100644
--- a/base/task/sequence_manager/work_queue_sets_unittest.cc
+++ b/base/task/sequence_manager/work_queue_sets_unittest.cc
@@ -148,7 +148,7 @@
   EXPECT_EQ(queue1, work_queue_sets_->GetOldestQueueInSet(set));
 }
 
-TEST_F(WorkQueueSetsTest, OnQueuesFrontTaskChanged_QueueBecomesEmpty) {
+TEST_F(WorkQueueSetsTest, OnQueuesFrontTaskChanged_OldestQueueBecomesEmpty) {
   WorkQueue* queue1 = NewTaskQueue("queue1");
   WorkQueue* queue2 = NewTaskQueue("queue2");
   WorkQueue* queue3 = NewTaskQueue("queue3");
@@ -166,6 +166,24 @@
   EXPECT_EQ(queue2, work_queue_sets_->GetOldestQueueInSet(set));
 }
 
+TEST_F(WorkQueueSetsTest, OnQueuesFrontTaskChanged_YoungestQueueBecomesEmpty) {
+  WorkQueue* queue1 = NewTaskQueue("queue1");
+  WorkQueue* queue2 = NewTaskQueue("queue2");
+  WorkQueue* queue3 = NewTaskQueue("queue3");
+  queue1->Push(FakeTaskWithEnqueueOrder(6));
+  queue2->Push(FakeTaskWithEnqueueOrder(5));
+  queue3->Push(FakeTaskWithEnqueueOrder(4));
+  size_t set = 4;
+  work_queue_sets_->ChangeSetIndex(queue1, set);
+  work_queue_sets_->ChangeSetIndex(queue2, set);
+  work_queue_sets_->ChangeSetIndex(queue3, set);
+  EXPECT_EQ(queue3, work_queue_sets_->GetOldestQueueInSet(set));
+
+  queue1->PopTaskForTesting();
+  work_queue_sets_->OnQueuesFrontTaskChanged(queue1);
+  EXPECT_EQ(queue3, work_queue_sets_->GetOldestQueueInSet(set));
+}
+
 TEST_F(WorkQueueSetsTest, OnPopMinQueueInSet) {
   WorkQueue* queue1 = NewTaskQueue("queue1");
   WorkQueue* queue2 = NewTaskQueue("queue2");
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 0f89ce3..4dd6502 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8916269455106018432
\ No newline at end of file
+8916129137480703632
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 80b9e33a..d041505 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8916272757229952512
\ No newline at end of file
+8916140137766260112
\ No newline at end of file
diff --git a/build/locale_tool.py b/build/locale_tool.py
index fce4435c..655c48e9 100755
--- a/build/locale_tool.py
+++ b/build/locale_tool.py
@@ -282,7 +282,6 @@
     _ExtractAllChromeLocalesLists()
   return _INTERNAL_CHROME_LOCALES
 
-
 def AndroidOmittedLocales():
   """Reutrn the list of locales omitted from Android APKs."""
   if not _INTERNAL_ANDROID_OMITTED_LOCALES:
@@ -380,7 +379,7 @@
   with open(file_path) as f:
     data = json.load(f)
     assert isinstance(data, list), "JSON file %s is not a list!" % file_path
-  return data
+  return [item.encode('utf8') for item in data]
 
 
 def _ExtractAllChromeLocalesLists():
@@ -410,6 +409,7 @@
     _INTERNAL_IOS_UNSUPPORTED_LOCALES = _ReadJsonList(
         os.path.join(out_path, 'foo.ios_unsupported_locales'))
 
+
 ##########################################################################
 ##########################################################################
 #####
@@ -702,9 +702,39 @@
   return errors
 
 
+# Regular expression used to replace the lang attribute inside .xtb files.
+_RE_TRANSLATIONBUNDLE = re.compile('<translationbundle lang="(.*)">')
+
+
+def _CreateFakeXtbFileFrom(src_xtb_path, dst_xtb_path, dst_locale):
+  """Create a fake .xtb file.
+
+  Args:
+    src_xtb_path: Path to source .xtb file to copy from.
+    dst_xtb_path: Path to destination .xtb file to write to.
+    dst_locale: Destination locale, the lang attribute in the source file
+      will be substituted with this value before its lines are written
+      to the destination file.
+  """
+  with open(src_xtb_path) as f:
+    src_xtb_lines = f.readlines()
+
+  def replace_xtb_lang_attribute(line):
+    m = _RE_TRANSLATIONBUNDLE.search(line)
+    if not m:
+      return line
+    return line[:m.start(1)] + dst_locale + line[m.end(1):]
+
+  dst_xtb_lines = [replace_xtb_lang_attribute(line) for line in src_xtb_lines]
+  with build_utils.AtomicOutput(dst_xtb_path) as tmp:
+    tmp.writelines(dst_xtb_lines)
+
+
 def _AddMissingLocalesInGrdTranslations(grd_file, grd_lines, wanted_locales):
   """Fix an input .grd line by adding missing Android outputs.
 
+  This also creates fake .xtb files from the one provided for 'en-GB'.
+
   Args:
     grd_file: Input .grd file path.
     grd_lines: Input .grd line list.
@@ -726,7 +756,7 @@
     if not missing_locales:
       continue
 
-    src_locale = 'bg'
+    src_locale = 'en-GB'
     src_lang_attribute = 'lang="%s"' % src_locale
     src_line = None
     for pos in xrange(start, end):
@@ -738,6 +768,10 @@
       raise Exception(
           'Cannot find <file> element with "%s" lang attribute' % src_locale)
 
+    src_path = os.path.join(
+        os.path.dirname(grd_file),
+        _RE_PATH_ATTRIBUTE.search(src_line).group(1))
+
     line_count = end - 1
     for locale in missing_locales:
       dst_line = src_line.replace(
@@ -746,6 +780,10 @@
       grd_lines.insert(line_count, dst_line)
       line_count += 1
 
+      dst_path = src_path.replace('_%s.xtb' % src_locale, '_%s.xtb' % locale)
+      _CreateFakeXtbFileFrom(src_path, dst_path, locale)
+
+
   # Sort the new <output> elements.
   return _SortGrdElementsRanges(grd_lines, _IsTranslationGrdOutputLine)
 
@@ -930,6 +968,7 @@
         input_file, input_lines, wanted_locales)
   return lines
 
+
 ##########################################################################
 ##########################################################################
 #####
diff --git a/chrome/VERSION b/chrome/VERSION
index 7e1fa72..6c72182 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=75
 MINOR=0
-BUILD=3766
+BUILD=3767
 PATCH=0
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
index f3cf7a4..277f7c90 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
@@ -287,6 +287,11 @@
         }
 
         @Override
+        public boolean hideOnScroll() {
+            return false;
+        }
+
+        @Override
         public int getSheetContentDescriptionStringId() {
             return R.string.autofill_assistant_sheet_content_description;
         }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
index ba5c858..bf65a036 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
@@ -12,7 +12,6 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
-import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -27,7 +26,7 @@
  * {@link TabListCoordinator} as well as the life-cycle of shared component
  * objects.
  */
-public class TabGridSheetCoordinator implements Destroyable {
+public class TabGridSheetCoordinator {
     final static String COMPONENT_NAME = "TabGridSheet";
     private final Context mContext;
     private final TabListCoordinator mTabGridCoordinator;
@@ -56,7 +55,6 @@
     /**
      * Destroy any members that needs clean up.
      */
-    @Override
     public void destroy() {
         mTabGridCoordinator.destroy();
         mMediator.destroy();
@@ -95,6 +93,7 @@
 
             if (mToolbarCoordinator != null) {
                 mToolbarCoordinator.destroy();
+                mToolbarCoordinator = null;
             }
         }
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java
index 46c8ca8..36d84f28 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java
@@ -12,7 +12,6 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.UrlConstants;
-import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -35,7 +34,7 @@
  * with the components' coordinator as well as managing the state of the bottom
  * sheet.
  */
-class TabGridSheetMediator implements Destroyable {
+class TabGridSheetMediator {
     /**
      * Defines an interface for a {@link TabGridSheetMediator} reset event handler.
      */
@@ -58,6 +57,7 @@
     private final ThemeColorProvider mThemeColorProvider;
     private final ThemeColorProvider.ThemeColorObserver mThemeColorObserver;
     private final ThemeColorProvider.TintObserver mTintObserver;
+    private final ResetHandler mResetHandler;
 
     TabGridSheetMediator(Context context, BottomSheetController bottomSheetController,
             ResetHandler resetHandler, PropertyModel model, TabModelSelector tabModelSelector,
@@ -68,6 +68,7 @@
         mTabModelSelector = tabModelSelector;
         mTabCreatorManager = tabCreatorManager;
         mThemeColorProvider = themeColorProvider;
+        mResetHandler = resetHandler;
 
         // TODO (ayman): Add instrumentation to observer calls.
         mSheetObserver = new EmptyBottomSheetObserver() {
@@ -83,19 +84,24 @@
         // register for tab model
         mTabModelObserver = new EmptyTabModelObserver() {
             @Override
-            public void didCloseTab(int tabId, boolean incognito) {
-                updateBottomSheetTitleAndMargin();
+            public void didAddTab(Tab tab, @TabLaunchType int type) {
+                updateBottomSheet();
             }
 
             @Override
-            public void didAddTab(Tab tab, @TabLaunchType int type) {
-                updateBottomSheetTitleAndMargin();
+            public void tabClosureUndone(Tab tab) {
+                updateBottomSheet();
             }
 
             @Override
             public void didSelectTab(Tab tab, int type, int lastId) {
                 if (type == TabSelectionType.FROM_USER) resetHandler.resetWithListOfTabs(null);
             }
+
+            @Override
+            public void willCloseTab(Tab tab, boolean animate) {
+                updateBottomSheet();
+            }
         };
         mThemeColorObserver =
                 (color, shouldAnimate) -> mModel.set(TabGridSheetProperties.PRIMARY_COLOR, color);
@@ -127,7 +133,6 @@
     /**
      * Destroy any members that needs clean up.
      */
-    @Override
     public void destroy() {
         if (mTabModelObserver != null) {
             mTabModelSelector.getTabModelFilterProvider().removeTabModelFilterObserver(
@@ -138,7 +143,7 @@
     }
 
     private void showTabGridSheet(TabGridSheetContent sheetContent) {
-        updateBottomSheetTitleAndMargin();
+        updateBottomSheet();
         mBottomSheetController.getBottomSheet().addObserver(mSheetObserver);
         mBottomSheetController.requestShowContent(sheetContent, true);
         mBottomSheetController.expandSheet();
@@ -155,13 +160,18 @@
                 : null;
     }
 
-    private void updateBottomSheetTitleAndMargin() {
+    private void updateBottomSheet() {
         Tab currentTab = mTabModelSelector.getCurrentTab();
         if (currentTab == null) return;
         int tabsCount = mTabModelSelector.getTabModelFilterProvider()
                                 .getCurrentTabModelFilter()
                                 .getRelatedTabList(currentTab.getId())
                                 .size();
+        if (tabsCount == 0) {
+            mResetHandler.resetWithListOfTabs(null);
+            return;
+        }
+
         mModel.set(TabGridSheetProperties.HEADER_TITLE,
                 mContext.getResources().getQuantityString(
                         R.plurals.bottom_tab_grid_title_placeholder, tabsCount, tabsCount));
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
index e8536d9..551bee06 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
@@ -68,7 +68,8 @@
     private final ThemeColorProvider.TintObserver mTintObserver;
     private final TabModelSelectorTabObserver mTabModelSelectorTabObserver;
     private final TabModelSelectorObserver mTabModelSelectorObserver;
-    private boolean mIsResetWithNonNullList;
+    private boolean mIsTabGroupUiVisible;
+    private boolean mIsClosingAGroup;
 
     TabGroupUiMediator(
             BottomControlsCoordinator.BottomControlsVisibilityController visibilityController,
@@ -87,13 +88,26 @@
         mTabModelObserver = new EmptyTabModelObserver() {
             @Override
             public void didSelectTab(Tab tab, @TabSelectionType int type, int lastId) {
-                if (type == TabSelectionType.FROM_CLOSE
-                        || getRelatedTabsForId(lastId).contains(tab))
-                    return;
+                if (!mIsTabGroupUiVisible) return;
+                if (type == TabSelectionType.FROM_CLOSE && !mIsClosingAGroup) return;
+                if (getRelatedTabsForId(lastId).contains(tab)) return;
                 resetTabStripWithRelatedTabsForId(tab.getId());
             }
 
             @Override
+            public void willCloseTab(Tab tab, boolean animate) {
+                if (!mIsTabGroupUiVisible) return;
+                Tab currentTab = mTabModelSelector.getCurrentTab();
+                if (currentTab == null) mResetHandler.resetSheetWithListOfTabs(null);
+                int tabsCount = mTabModelSelector.getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter()
+                                        .getRelatedTabList(currentTab.getId())
+                                        .size();
+
+                mIsClosingAGroup = tabsCount == 0;
+            }
+
+            @Override
             public void didAddTab(Tab tab, int type) {
                 if (type == TabLaunchType.FROM_CHROME_UI || type == TabLaunchType.FROM_RESTORE)
                     return;
@@ -128,7 +142,7 @@
                                                .getRelatedTabList(tab.getId());
                 int numTabs = listOfTabs.size();
                 // This is set to zero because the UI is hidden.
-                if (!mIsResetWithNonNullList) numTabs = 0;
+                if (!mIsTabGroupUiVisible) numTabs = 0;
                 RecordHistogram.recordCountHistogram("TabStrip.TabCountOnPageLoad", numTabs);
             }
         };
@@ -190,12 +204,12 @@
                                        .getRelatedTabList(id);
         if (listOfTabs.size() < 2) {
             mResetHandler.resetStripWithListOfTabs(null);
-            mIsResetWithNonNullList = false;
+            mIsTabGroupUiVisible = false;
         } else {
             mResetHandler.resetStripWithListOfTabs(listOfTabs);
-            mIsResetWithNonNullList = true;
+            mIsTabGroupUiVisible = true;
         }
-        mVisibilityController.setBottomControlsVisible(mIsResetWithNonNullList);
+        mVisibilityController.setBottomControlsVisible(mIsTabGroupUiVisible);
     }
 
     private List<Tab> getRelatedTabsForId(int id) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index d8de33f2..c72995a 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -304,6 +304,8 @@
 
         mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(mTabModelObserver);
 
+        // TODO(meiliang): follow up with unit tests to test the close signal is sent correctly with
+        // the recommendedNextTab.
         mTabClosedListener = new TabActionListener() {
             @Override
             public void run(int tabId) {
@@ -318,9 +320,32 @@
                     }
                 }
                 onTabClosedFrom(tabId, mComponentName);
+
+                Tab currentTab = mTabModelSelector.getCurrentTab();
+                Tab closingTab =
+                        TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId);
+                Tab nextTab = currentTab == closingTab ? getNextTab(tabId) : null;
+
                 mTabModelSelector.getCurrentModel().closeTab(
-                        TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId), false,
-                        false, true);
+                        closingTab, nextTab, false, false, true);
+            }
+
+            private Tab getNextTab(int closingTabId) {
+                int closingTabIndex = mModel.indexFromId(closingTabId);
+
+                if (closingTabIndex == TabModel.INVALID_TAB_INDEX) {
+                    assert false;
+                    return null;
+                }
+
+                int nextTabId = Tab.INVALID_TAB_ID;
+                if (mModel.size() > 1) {
+                    nextTabId = closingTabIndex == 0
+                            ? mModel.get(closingTabIndex + 1).get(TabProperties.TAB_ID)
+                            : mModel.get(closingTabIndex - 1).get(TabProperties.TAB_ID);
+                }
+
+                return TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), nextTabId);
             }
         };
     }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index dbbbafca..5789fa1 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -184,7 +184,7 @@
                 .get(TabProperties.TAB_CLOSED_LISTENER)
                 .run(mModel.get(1).get(TabProperties.TAB_ID));
 
-        verify(mTabModel).closeTab(eq(mTab2), eq(false), eq(false), eq(true));
+        verify(mTabModel).closeTab(eq(mTab2), eq(null), eq(false), eq(false), eq(true));
     }
 
     @Test
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 109eec8..3d9899b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -615,8 +615,15 @@
 
                 private void closeIfNoTabsAndHomepageEnabled(boolean isPendingClosure) {
                     if (getTabModelSelector().getTotalTabCount() == 0) {
-                        // If the last tab is closed, and homepage is enabled, then exit Chrome.
-                        if (HomepageManager.shouldCloseAppWithZeroTabs()) {
+                        // If the last tab is closed, and one of the following is true, then exit
+                        // Chrome:
+                        //   1. If homepage is enabled.
+                        //   2. If TabGroupsAndroid is enabled, and isPendingClosure is true.
+                        //      isPendingClosure is used to avoid calling finish() when closing all
+                        //      tabs in tab switcher.
+                        if (HomepageManager.shouldCloseAppWithZeroTabs()
+                                || (FeatureUtilities.isTabGroupsAndroidEnabled()
+                                        && isPendingClosure)) {
                             finish();
                         } else if (isPendingClosure) {
                             NewTabPageUma.recordNTPImpression(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModel.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModel.java
index e61df2a..434d140b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModel.java
@@ -111,6 +111,12 @@
     }
 
     @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        return closeTab(tab, animate, uponExit, canUndo);
+    }
+
+    @Override
     public TabList getComprehensiveModel() {
         return this;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModel.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModel.java
index 01f57bf..3146767 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModel.java
@@ -132,6 +132,15 @@
     }
 
     @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        boolean retVal =
+                mDelegateModel.closeTab(tab, recommendedNextTab, animate, uponExit, canUndo);
+        destroyIncognitoIfNecessary();
+        return retVal;
+    }
+
+    @Override
     public Tab getNextTabIfClosed(int id) {
         return mDelegateModel.getNextTabIfClosed(id);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/SingleTabModel.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/SingleTabModel.java
index da2066b..b1140bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/SingleTabModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/SingleTabModel.java
@@ -99,6 +99,12 @@
         return false;
     }
 
+    @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        return closeTab(tab, animate, uponExit, canUndo);
+    }
+
     /**
      * In webapps, calls finish on the activity, but keeps it in recents. In Document mode,
      * finishes and removes from recents. We use mBlockNewWindows flag to distinguish the user
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModel.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModel.java
index 21528b8..0d9371da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModel.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.tabmodel;
 
+import android.support.annotation.Nullable;
+
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 
@@ -45,6 +47,28 @@
     public boolean closeTab(Tab tab, boolean animate, boolean uponExit, boolean canUndo);
 
     /**
+     * Unregisters and destroys the specified tab, and then switches to {@code recommendedNextTab}
+     * if it is not null, otherwise switches to the previous tab.
+     *
+     * @param tab The non-null tab to close.
+     * @param recommendedNextTab The tab to switch to if not null.
+     * @param animate true iff the closing animation should be displayed.
+     * @param uponExit true iff the tab is being closed upon application exit (after user presses
+     *                 the system back button).
+     * @param canUndo Whether or not this action can be undone. If this is {@code true} and
+     *                {@link #supportsPendingClosures()} is {@code true}, this {@link Tab}
+     *                will not actually be closed until {@link #commitTabClosure(int)} or
+     *                {@link #commitAllTabClosures()} is called, but it will be effectively removed
+     *                from this list. To get a comprehensive list of all tabs, including ones that
+     *                have been partially closed, use the {@link TabList} from
+     *                {@link #getComprehensiveModel()}.
+     *
+     * @return true if the tab was found.
+     */
+    public boolean closeTab(Tab tab, @Nullable Tab recommendedNextTab, boolean animate,
+            boolean uponExit, boolean canUndo);
+
+    /**
      * Returns which tab would be selected if the specified tab {@code id} were closed.
      * @param id The ID of tab which would be closed.
      * @return The id of the next tab that would be visible.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
index f68e8cb..724cb13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
@@ -95,7 +95,7 @@
 
     @Override
     public void removeTab(Tab tab) {
-        removeTabAndSelectNext(tab, TabSelectionType.FROM_CLOSE, false, true);
+        removeTabAndSelectNext(tab, null, TabSelectionType.FROM_CLOSE, false, true);
 
         for (TabModelObserver obs : mObservers) obs.tabRemoved(tab);
     }
@@ -348,7 +348,13 @@
 
     @Override
     public boolean closeTab(Tab tabToClose, boolean animate, boolean uponExit, boolean canUndo) {
-        return closeTab(tabToClose, animate, uponExit, canUndo, canUndo);
+        return closeTab(tabToClose, null, animate, uponExit, canUndo, canUndo);
+    }
+
+    @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        return closeTab(tab, recommendedNextTab, animate, uponExit, canUndo, canUndo);
     }
 
     /**
@@ -359,8 +365,8 @@
      *               closure. Observers will still be notified of a committed/cancelled closure
      *               even if they are not notified of a pending closure to start with.
      */
-    private boolean closeTab(Tab tabToClose, boolean animate, boolean uponExit,
-            boolean canUndo, boolean notify) {
+    private boolean closeTab(Tab tabToClose, Tab recommendedNextTab, boolean animate,
+            boolean uponExit, boolean canUndo, boolean notify) {
         if (tabToClose == null) {
             assert false : "Tab is null!";
             return false;
@@ -373,7 +379,7 @@
 
         canUndo &= supportsPendingClosures();
 
-        startTabClosure(tabToClose, animate, uponExit, canUndo);
+        startTabClosure(tabToClose, recommendedNextTab, animate, uponExit, canUndo);
         if (notify && canUndo) {
             for (TabModelObserver obs : mObservers) obs.tabPendingClosure(tabToClose);
         }
@@ -390,7 +396,7 @@
                 continue;
             }
             tab.setClosing(true);
-            closeTab(tab, false, false, canUndo, false);
+            closeTab(tab, null, false, false, canUndo, false);
         }
         if (canUndo && supportsPendingClosures()) {
             for (TabModelObserver obs : mObservers) obs.multipleTabsPendingClosure(tabs, false);
@@ -452,7 +458,7 @@
         while (getCount() > 0) {
             Tab tab = getTabAt(0);
             closedTabs.add(tab);
-            closeTab(tab, animate, uponExit, canUndo, false);
+            closeTab(tab, null, animate, uponExit, canUndo, false);
         }
 
         if (!uponExit && canUndo && supportsPendingClosures()) {
@@ -548,7 +554,8 @@
      *                {@link #commitTabClosure(int)} or {@link #commitAllTabClosures()} needs to be
      *                called to actually delete and clean up {@code tab}.
      */
-    private void startTabClosure(Tab tab, boolean animate, boolean uponExit, boolean canUndo) {
+    private void startTabClosure(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
         tab.setClosing(true);
 
         for (TabModelObserver obs : mObservers) obs.willCloseTab(tab, animate);
@@ -557,14 +564,15 @@
         int selectionType = uponExit ? TabSelectionType.FROM_EXIT : TabSelectionType.FROM_CLOSE;
         boolean pauseMedia = canUndo;
         boolean updateRewoundList = !canUndo;
-        removeTabAndSelectNext(tab, selectionType, pauseMedia, updateRewoundList);
+        removeTabAndSelectNext(
+                tab, recommendedNextTab, selectionType, pauseMedia, updateRewoundList);
     }
 
     /**
      * Removes the given tab from the tab model and selects a new tab.
      */
-    private void removeTabAndSelectNext(Tab tab, @TabSelectionType int selectionType,
-            boolean pauseMedia, boolean updateRewoundList) {
+    private void removeTabAndSelectNext(Tab tab, Tab recommendedNextTab,
+            @TabSelectionType int selectionType, boolean pauseMedia, boolean updateRewoundList) {
         assert selectionType == TabSelectionType.FROM_CLOSE
                 || selectionType == TabSelectionType.FROM_EXIT;
 
@@ -573,7 +581,8 @@
 
         Tab currentTabInModel = TabModelUtils.getCurrentTab(this);
         Tab adjacentTabInModel = getTabAt(closingTabIndex == 0 ? 1 : closingTabIndex - 1);
-        Tab nextTab = getNextTabIfClosed(closingTabId);
+        Tab nextTab =
+                recommendedNextTab == null ? getNextTabIfClosed(closingTabId) : recommendedNextTab;
 
         // TODO(dtrainor): Update the list of undoable tabs instead of committing it.
         if (updateRewoundList) commitAllTabClosures();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
index c0d59c2..cff02c8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
@@ -266,6 +266,12 @@
     }
 
     @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        return closeTab(tab, animate, uponExit, canUndo);
+    }
+
+    @Override
     protected TabDelegate getTabCreator(boolean incognito) {
         return null;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/document/MockDocumentTabModel.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/document/MockDocumentTabModel.java
index 8b1ca6e..a1616aa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/document/MockDocumentTabModel.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/document/MockDocumentTabModel.java
@@ -44,6 +44,12 @@
     }
 
     @Override
+    public boolean closeTab(
+            Tab tab, Tab recommendedNextTab, boolean animate, boolean uponExit, boolean canUndo) {
+        return closeTab(tab, animate, uponExit, canUndo);
+    }
+
+    @Override
     public Tab getNextTabIfClosed(int id) {
         Assert.fail();
         return null;
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DOUBLE_CLICK.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DOUBLE_CLICK.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DOUBLE_CLICK.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DOUBLE_CLICK.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DRAG_AND_DROP.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DRAG_AND_DROP.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DRAG_AND_DROP.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_DRAG_AND_DROP.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LABEL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LABEL.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LABEL.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LABEL.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LEFT_CLICK.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LEFT_CLICK.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LEFT_CLICK.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_LEFT_CLICK.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_NO_ACTION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_NO_ACTION.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_NO_ACTION.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_NO_ACTION.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_RIGHT_CLICK.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_RIGHT_CLICK.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_RIGHT_CLICK.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_EVENT_TYPE_RIGHT_CLICK.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_DEFAULT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_DEFAULT.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_DEFAULT.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_DEFAULT.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_LARGE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_LARGE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_LARGE.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_LARGE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_SMALL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_SMALL.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_SMALL.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_EXTRA_SMALL.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LABEL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LABEL.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LABEL.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LABEL.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LARGE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LARGE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LARGE.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_LARGE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_SMALL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_SMALL.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_SMALL.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_MOVEMENT_THRESHOLD_SMALL.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_REVERT_TO_LEFT_CLICK.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_REVERT_TO_LEFT_CLICK.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_SETTINGS_AUTOCLICK_REVERT_TO_LEFT_CLICK.png.sha1
rename to chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOCLICK_REVERT_TO_LEFT_CLICK.png.sha1
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 1eeec1f..2ff583c6 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -874,6 +874,11 @@
     "file_manager/file_manager_string_util.h",
     "file_manager/file_tasks.cc",
     "file_manager/file_tasks.h",
+    "file_manager/file_tasks_notifier.cc",
+    "file_manager/file_tasks_notifier.h",
+    "file_manager/file_tasks_notifier_factory.cc",
+    "file_manager/file_tasks_notifier_factory.h",
+    "file_manager/file_tasks_observer.h",
     "file_manager/file_watcher.cc",
     "file_manager/file_watcher.h",
     "file_manager/fileapi_util.cc",
@@ -2344,6 +2349,7 @@
     "extensions/wallpaper_private_api_unittest.cc",
     "external_metrics_unittest.cc",
     "file_manager/documents_provider_root_manager_unittest.cc",
+    "file_manager/file_tasks_notifier_unittest.cc",
     "file_manager/file_tasks_unittest.cc",
     "file_manager/file_watcher_unittest.cc",
     "file_manager/fileapi_util_unittest.cc",
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.cc b/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.cc
index c5069a6..0108f0e 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.cc
+++ b/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.cc
@@ -12,8 +12,6 @@
 namespace arc {
 
 const char kContentFileSystemMountPointName[] = "arc-content";
-const char kIntentHelperFileproviderUrl[] =
-    "content://org.chromium.arc.intent_helper.fileprovider/";
 const char kFileSystemFileproviderUrl[] =
     "content://org.chromium.arc.file_system.fileprovider/";
 
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h b/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h
index 61f4a3d..b4633ca2 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h
+++ b/chrome/browser/chromeos/arc/fileapi/arc_content_file_system_url_util.h
@@ -19,10 +19,7 @@
 // The name of the ARC content file system mount point.
 extern const char kContentFileSystemMountPointName[];
 
-// ARC FileProvider URLs.
-// TODO(niwa): Remove kIntentHelperFileproviderUrl once we completely move
-// ARC FileProvider to arc.file_system (b/111816608).
-extern const char kIntentHelperFileproviderUrl[];
+// ARC FileProvider URL.
 extern const char kFileSystemFileproviderUrl[];
 
 // The path of the ARC content file system mount point.
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_select_files_handler.cc b/chrome/browser/chromeos/arc/fileapi/arc_select_files_handler.cc
index f182ce4..a6c29d9 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_select_files_handler.cc
+++ b/chrome/browser/chromeos/arc/fileapi/arc_select_files_handler.cc
@@ -95,16 +95,7 @@
                          const std::vector<GURL>& content_urls) {
   mojom::SelectFilesResultPtr result = mojom::SelectFilesResult::New();
   for (const GURL& content_url : content_urls) {
-    // Replace intent_helper.fileprovider with file_system.fileprovider in URL.
-    // TODO(niwa): Remove this and update path_util to use
-    // file_system.fileprovider by default once we complete migration.
-    std::string url_string = content_url.spec();
-    if (base::StartsWith(url_string, arc::kIntentHelperFileproviderUrl,
-                         base::CompareCase::INSENSITIVE_ASCII)) {
-      url_string.replace(0, strlen(arc::kIntentHelperFileproviderUrl),
-                         arc::kFileSystemFileproviderUrl);
-    }
-    result->urls.push_back(GURL(url_string));
+    result->urls.push_back(content_url);
   }
   std::move(callback).Run(std::move(result));
 }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
index fa4477c3..34cefa7 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/extensions/chrome_extension_function_details.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -78,11 +79,12 @@
       render_frame_host(), chrome_details.GetProfile(), file_paths, option,
       base::BindOnce(
           &FileManagerPrivateSelectFileFunction::GetSelectedFileInfoResponse,
-          this, params->index));
+          this, params->for_opening, params->index));
   return RespondLater();
 }
 
 void FileManagerPrivateSelectFileFunction::GetSelectedFileInfoResponse(
+    bool for_open,
     int index,
     const std::vector<ui::SelectedFileInfo>& files) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -92,6 +94,12 @@
   }
   SelectFileDialogExtension::OnFileSelected(GetFileDialogRoutingID(this),
                                             files[0], index);
+  ChromeExtensionFunctionDetails chrome_details(this);
+  if (auto* notifier =
+          file_manager::file_tasks::FileTasksNotifier::GetForProfile(
+              chrome_details.GetProfile())) {
+    notifier->NotifyFileDialogSelection({files[0]}, for_open);
+  }
   Respond(NoArguments());
 }
 
@@ -112,11 +120,12 @@
           : file_manager::util::NO_LOCAL_PATH_RESOLUTION,
       base::BindOnce(
           &FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse,
-          this));
+          this, true));
   return RespondLater();
 }
 
 void FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse(
+    bool for_open,
     const std::vector<ui::SelectedFileInfo>& files) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (files.empty()) {
@@ -126,6 +135,12 @@
 
   SelectFileDialogExtension::OnMultiFilesSelected(GetFileDialogRoutingID(this),
                                                   files);
+  ChromeExtensionFunctionDetails chrome_details(this);
+  if (auto* notifier =
+          file_manager::file_tasks::FileTasksNotifier::GetForProfile(
+              chrome_details.GetProfile())) {
+    notifier->NotifyFileDialogSelection(files, for_open);
+  }
   Respond(NoArguments());
 }
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.h b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.h
index 093081e..70acf95d 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.h
@@ -47,6 +47,7 @@
  private:
   // A callback method to handle the result of GetSelectedFileInfo.
   void GetSelectedFileInfoResponse(
+      bool for_open,
       int index,
       const std::vector<ui::SelectedFileInfo>& files);
 };
@@ -67,6 +68,7 @@
  private:
   // A callback method to handle the result of GetSelectedFileInfo.
   void GetSelectedFileInfoResponse(
+      bool for_open,
       const std::vector<ui::SelectedFileInfo>& files);
 };
 
diff --git a/chrome/browser/chromeos/file_manager/arc_file_tasks.cc b/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
index 867ea05..d85d9775 100644
--- a/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/arc_file_tasks.cc
@@ -115,19 +115,9 @@
   request->action_type = FileTaskActionIdToArcActionType(task.action_id);
   request->activity_name = AppIdToActivityName(task.app_id);
   for (size_t i = 0; i < content_urls.size(); ++i) {
-    // Replace intent_helper.fileprovider with file_system.fileprovider in URL.
-    // TODO(niwa): Remove this and update path_util to use
-    // file_system.fileprovider by default once we complete migration.
-    std::string url_string = content_urls[i].spec();
-    if (base::StartsWith(url_string, arc::kIntentHelperFileproviderUrl,
-                         base::CompareCase::INSENSITIVE_ASCII)) {
-      url_string.replace(0, strlen(arc::kIntentHelperFileproviderUrl),
-                         arc::kFileSystemFileproviderUrl);
-    }
-
     arc::mojom::ContentUrlWithMimeTypePtr url_with_type =
         arc::mojom::ContentUrlWithMimeType::New();
-    url_with_type->content_url = GURL(url_string);
+    url_with_type->content_url = content_urls[i];
     url_with_type->mime_type = mime_types[i];
     request->urls.push_back(std::move(url_with_type));
   }
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index f08c8d0..9a22dcd 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -38,6 +38,8 @@
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/file_manager/app_id.h"
 #include "chrome/browser/chromeos/file_manager/file_manager_test_util.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_observer.h"
 #include "chrome/browser/chromeos/file_manager/mount_test_util.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
@@ -91,6 +93,7 @@
 #include "services/service_manager/public/cpp/connector.h"
 #include "storage/browser/fileapi/external_mount_points.h"
 #include "storage/browser/fileapi/file_system_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/events/event.h"
@@ -577,8 +580,67 @@
   GetLockForBlockingDefaultFileTaskRunner().Release();
 }
 
+struct ExpectFileTasksMessage {
+  static bool ConvertJSONValue(const base::DictionaryValue& value,
+                               ExpectFileTasksMessage* message) {
+    base::JSONValueConverter<ExpectFileTasksMessage> converter;
+    return converter.Convert(value, message);
+  }
+
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<ExpectFileTasksMessage>* converter) {
+    converter->RegisterCustomField(
+        "openType", &ExpectFileTasksMessage::open_type, &MapStringToOpenType);
+    converter->RegisterRepeatedString("fileNames",
+                                      &ExpectFileTasksMessage::file_names);
+  }
+
+  static bool MapStringToOpenType(
+      base::StringPiece value,
+      file_tasks::FileTasksObserver::OpenType* open_type) {
+    using OpenType = file_tasks::FileTasksObserver::OpenType;
+    if (value == "launch") {
+      *open_type = OpenType::kLaunch;
+    } else if (value == "open") {
+      *open_type = OpenType::kOpen;
+    } else if (value == "saveAs") {
+      *open_type = OpenType::kSaveAs;
+    } else if (value == "download") {
+      *open_type = OpenType::kDownload;
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  std::vector<std::unique_ptr<std::string>> file_names;
+  file_tasks::FileTasksObserver::OpenType open_type;
+};
+
 }  // anonymous namespace
 
+class FileManagerBrowserTestBase::MockFileTasksObserver
+    : public file_tasks::FileTasksObserver {
+ public:
+  explicit MockFileTasksObserver(Profile* profile) : observer_(this) {
+    observer_.Add(file_tasks::FileTasksNotifier::GetForProfile(profile));
+  }
+
+  MOCK_METHOD2(OnFilesOpenedImpl,
+               void(const std::string& path, OpenType open_type));
+
+  void OnFilesOpened(const std::vector<FileOpenEvent>& opens) {
+    ASSERT_TRUE(!opens.empty());
+    for (auto& open : opens) {
+      OnFilesOpenedImpl(open.path.value(), open.open_type);
+    }
+  }
+
+ private:
+  ScopedObserver<file_tasks::FileTasksNotifier, file_tasks::FileTasksObserver>
+      observer_;
+};
+
 // LocalTestVolume: test volume for a local drive.
 class LocalTestVolume : public TestVolume {
  public:
@@ -1591,6 +1653,14 @@
         android_files_volume_->Mount(profile());
       }
     }
+
+    if (!IsIncognitoModeTest()) {
+      file_tasks_observer_ =
+          std::make_unique<testing::StrictMock<MockFileTasksObserver>>(
+              profile());
+    } else {
+      EXPECT_FALSE(file_tasks::FileTasksNotifier::GetForProfile(profile()));
+    }
   }
 
   display_service_ =
@@ -2163,6 +2233,21 @@
     return;
   }
 
+  if (name == "expectFileTask") {
+    ExpectFileTasksMessage message;
+    ASSERT_TRUE(ExpectFileTasksMessage::ConvertJSONValue(value, &message));
+    // FileTasksNotifier is disabled in incognito or guest profiles.
+    if (!file_tasks_observer_) {
+      return;
+    }
+    for (const auto& file_name : message.file_names) {
+      EXPECT_CALL(
+          *file_tasks_observer_,
+          OnFilesOpenedImpl(testing::HasSubstr(*file_name), message.open_type));
+    }
+    return;
+  }
+
   FAIL() << "Unknown test message: " << name;
 }
 
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
index 2d47e0a3..e5f6b5c 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h
@@ -73,6 +73,8 @@
   void StartTest();
 
  private:
+  class MockFileTasksObserver;
+
   // Returns true if the test requires incognito mode.
   bool IsIncognitoModeTest() const { return GetGuestMode() == IN_INCOGNITO; }
 
@@ -165,6 +167,8 @@
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
   std::unique_ptr<arc::FakeFileSystemInstance> arc_file_system_instance_;
 
+  std::unique_ptr<MockFileTasksObserver> file_tasks_observer_;
+
   // Not owned.
   SelectFileDialogExtensionTestFactory* select_factory_;
 
diff --git a/chrome/browser/chromeos/file_manager/file_tasks.cc b/chrome/browser/chromeos/file_manager/file_tasks.cc
index d9824c4..0cc78d3f 100644
--- a/chrome/browser/chromeos/file_manager/file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/file_tasks.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/chromeos/file_manager/arc_file_tasks.h"
 #include "chrome/browser/chromeos/file_manager/crostini_file_tasks.h"
 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
 #include "chrome/browser/chromeos/file_manager/open_util.h"
 #include "chrome/browser/chromeos/file_manager/open_with_browser.h"
@@ -358,6 +359,10 @@
                               task.task_type, NUM_TASK_TYPE);
   }
 
+  if (auto* notifier = FileTasksNotifier::GetForProfile(profile)) {
+    notifier->NotifyFileTasks(file_urls);
+  }
+
   // ARC apps needs mime types for launching. Retrieve them first.
   if (task.task_type == TASK_TYPE_ARC_APP) {
     extensions::app_file_handler_util::MimeTypeCollector* mime_collector =
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc b/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc
new file mode 100644
index 0000000..c05d66a
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier.cc
@@ -0,0 +1,107 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
+
+#include "base/callback.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_observer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/download/public/common/download_item.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "ui/shell_dialogs/selected_file_info.h"
+
+namespace file_manager {
+namespace file_tasks {
+namespace {
+
+bool IsSupportedFileSystemType(storage::FileSystemType type) {
+  switch (type) {
+    case storage::kFileSystemTypeNativeLocal:
+    case storage::kFileSystemTypeRestrictedNativeLocal:
+    case storage::kFileSystemTypeDriveFs:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
+FileTasksNotifier::FileTasksNotifier(Profile* profile)
+    : profile_(profile),
+      download_notifier_(content::BrowserContext::GetDownloadManager(profile_),
+                         this) {}
+
+FileTasksNotifier::~FileTasksNotifier() = default;
+
+// static
+FileTasksNotifier* FileTasksNotifier::GetForProfile(Profile* profile) {
+  return FileTasksNotifierFactory::GetInstance()->GetForProfile(profile);
+}
+
+void FileTasksNotifier::AddObserver(FileTasksObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void FileTasksNotifier::RemoveObserver(FileTasksObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void FileTasksNotifier::OnDownloadUpdated(content::DownloadManager* manager,
+                                          download::DownloadItem* item) {
+  if (item->IsTransient() ||
+      item->GetState() != download::DownloadItem::DownloadState::COMPLETE ||
+      item->GetDownloadCreationType() ==
+          download::DownloadItem::DownloadCreationType::TYPE_HISTORY_IMPORT) {
+    return;
+  }
+  NotifyObservers({item->GetTargetFilePath()},
+                  FileTasksObserver::OpenType::kDownload);
+}
+
+void FileTasksNotifier::NotifyFileTasks(
+    const std::vector<storage::FileSystemURL>& file_urls) {
+  std::vector<base::FilePath> paths;
+  for (const auto& url : file_urls) {
+    if (IsSupportedFileSystemType(url.type())) {
+      paths.push_back(url.path());
+    }
+  }
+  NotifyObservers(paths, FileTasksObserver::OpenType::kLaunch);
+}
+
+void FileTasksNotifier::NotifyFileDialogSelection(
+    const std::vector<ui::SelectedFileInfo>& files,
+    bool for_open) {
+  std::vector<base::FilePath> paths;
+  for (const auto& file : files) {
+    paths.push_back(file.file_path);
+  }
+  NotifyObservers(paths, for_open ? FileTasksObserver::OpenType::kOpen
+                                  : FileTasksObserver::OpenType::kSaveAs);
+}
+
+void FileTasksNotifier::NotifyObservers(
+    const std::vector<base::FilePath>& paths,
+    FileTasksObserver::OpenType open_type) {
+  std::vector<FileTasksObserver::FileOpenEvent> opens;
+  for (const auto& path : paths) {
+    if (profile_->GetPath().IsParent(path) ||
+        base::FilePath("/run/arc/sdcard/write/emulated/0").IsParent(path) ||
+        base::FilePath("/media/fuse").IsParent(path)) {
+      opens.push_back({path, open_type});
+    }
+  }
+  if (opens.empty()) {
+    return;
+  }
+  for (auto& observer : observers_) {
+    observer.OnFilesOpened(opens);
+  }
+}
+
+}  // namespace file_tasks
+}  // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier.h b/chrome/browser/chromeos/file_manager/file_tasks_notifier.h
new file mode 100644
index 0000000..8a9b7c3
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier.h
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_H_
+#define CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_H_
+
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/observer_list.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_observer.h"
+#include "components/download/content/public/all_download_item_notifier.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace storage {
+class FileSystemURL;
+}  // namespace storage
+
+namespace ui {
+struct SelectedFileInfo;
+}
+
+namespace file_manager {
+namespace file_tasks {
+
+class FileTasksNotifier : public KeyedService,
+                          public download::AllDownloadItemNotifier::Observer {
+ public:
+  enum class FileAvailability {
+    // File exists and is accessible.
+    kOk,
+
+    // The file exists but is currently unavailable; e.g. a Drive file while
+    // offline
+    kTemporarilyUnavailable,
+
+    // The current state is unknown; e.g. crostini isn't running
+    kUnknown,
+
+    // The file is known to be deleted.
+    kGone,
+  };
+
+  explicit FileTasksNotifier(Profile* profile);
+  ~FileTasksNotifier() override;
+
+  static FileTasksNotifier* GetForProfile(Profile* profile);
+
+  void AddObserver(FileTasksObserver*);
+  void RemoveObserver(FileTasksObserver*);
+
+  void NotifyFileTasks(const std::vector<storage::FileSystemURL>& file_urls);
+
+  void NotifyFileDialogSelection(const std::vector<ui::SelectedFileInfo>& files,
+                                 bool for_open);
+
+  // download::AllDownloadItemNotifier::Observer:
+  void OnDownloadUpdated(content::DownloadManager* manager,
+                         download::DownloadItem* item) override;
+
+ private:
+  void NotifyObservers(const std::vector<base::FilePath>& paths,
+                       FileTasksObserver::OpenType open_type);
+
+  Profile* const profile_;
+  download::AllDownloadItemNotifier download_notifier_;
+  base::ObserverList<FileTasksObserver> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileTasksNotifier);
+};
+
+}  // namespace file_tasks
+}  // namespace file_manager
+
+#endif  // CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_H_
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.cc b/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.cc
new file mode 100644
index 0000000..f9e7fa0
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.cc
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h"
+
+#include "base/no_destructor.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace file_manager {
+namespace file_tasks {
+
+FileTasksNotifierFactory ::FileTasksNotifierFactory()
+    : BrowserContextKeyedServiceFactory(
+          "FileTasksNotifier",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+FileTasksNotifierFactory* FileTasksNotifierFactory::GetInstance() {
+  static base::NoDestructor<FileTasksNotifierFactory> instance;
+  return instance.get();
+}
+
+FileTasksNotifier* FileTasksNotifierFactory::GetForProfile(Profile* profile) {
+  return static_cast<FileTasksNotifier*>(
+      GetServiceForBrowserContext(profile, true));
+}
+
+KeyedService* FileTasksNotifierFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new FileTasksNotifier(Profile::FromBrowserContext(context));
+}
+
+}  // namespace file_tasks
+}  // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h b/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h
new file mode 100644
index 0000000..b659054
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier_factory.h
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_FACTORY_H_
+#define CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_FACTORY_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace file_manager {
+namespace file_tasks {
+
+class FileTasksNotifier;
+
+class FileTasksNotifierFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  FileTasksNotifierFactory();
+
+  static FileTasksNotifierFactory* GetInstance();
+
+  FileTasksNotifier* GetForProfile(Profile* profile);
+
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileTasksNotifierFactory);
+};
+
+}  // namespace file_tasks
+}  // namespace file_manager
+
+#endif  // CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_NOTIFIER_FACTORY_H_
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc b/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc
new file mode 100644
index 0000000..3151548
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_notifier_unittest.cc
@@ -0,0 +1,356 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
+
+#include <memory>
+
+#include "base/scoped_observer.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_observer.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/fake_download_item.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/shell_dialogs/selected_file_info.h"
+
+namespace file_manager {
+namespace file_tasks {
+namespace {
+
+using testing::_;
+
+storage::FileSystemURL CreateFileSystemUrl(
+    const base::FilePath& path,
+    storage::FileSystemType type = storage::kFileSystemTypeNativeLocal) {
+  return storage::FileSystemURL::CreateForTest({}, {}, {}, "", type, path, "",
+                                               {});
+}
+
+ui::SelectedFileInfo CreateSelectedFileInfo(
+    const base::FilePath& path,
+    const base::FilePath& local_path = {}) {
+  return ui::SelectedFileInfo(path, local_path);
+}
+
+class MockFileTasksObserver : public file_tasks::FileTasksObserver {
+ public:
+  explicit MockFileTasksObserver(FileTasksNotifier* notifier)
+      : observer_(this) {
+    observer_.Add(notifier);
+  }
+
+  MOCK_METHOD2(OnFilesOpenedImpl,
+               void(const base::FilePath& path, OpenType open_type));
+
+  void OnFilesOpened(const std::vector<FileOpenEvent>& opens) {
+    ASSERT_TRUE(!opens.empty());
+    for (auto& open : opens) {
+      OnFilesOpenedImpl(open.path, open.open_type);
+    }
+  }
+
+ private:
+  ScopedObserver<file_tasks::FileTasksNotifier, file_tasks::FileTasksObserver>
+      observer_;
+};
+
+class FileTasksNotifierTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    profile_ = std::make_unique<TestingProfile>();
+    notifier_ = std::make_unique<FileTasksNotifier>(profile_.get());
+    observer_ = std::make_unique<MockFileTasksObserver>(notifier_.get());
+  }
+
+  void TearDown() override {
+    observer_.reset();
+    notifier_.reset();
+    profile_.reset();
+  }
+
+  Profile& profile() { return *profile_; }
+  MockFileTasksObserver& observer() { return *observer_; }
+  FileTasksNotifier& notifier() { return *notifier_; }
+
+  download::DownloadItem* CreateCompletedDownloadItem(
+      const base::FilePath& path) {
+    download_item_ = std::make_unique<content::FakeDownloadItem>();
+    download_item_->SetTargetFilePath(path);
+    download_item_->SetState(download::DownloadItem::DownloadState::COMPLETE);
+    return download_item_.get();
+  }
+
+ private:
+  content::TestBrowserThreadBundle threads_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<FileTasksNotifier> notifier_;
+  std::unique_ptr<MockFileTasksObserver> observer_;
+  std::unique_ptr<content::FakeDownloadItem> download_item_;
+};
+
+TEST_F(FileTasksNotifierTest, FileTask_Local) {
+  base::FilePath path = profile().GetPath().Append("file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kLaunch));
+  notifier().NotifyFileTasks({CreateFileSystemUrl(path)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_DriveFs) {
+  base::FilePath path("/media/fuse/drivefs-abcedf/root/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kLaunch));
+  notifier().NotifyFileTasks(
+      {CreateFileSystemUrl(path, storage::kFileSystemTypeDriveFs)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_Arc) {
+  base::FilePath path("/run/arc/sdcard/write/emulated/0/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kLaunch));
+  notifier().NotifyFileTasks({CreateFileSystemUrl(path)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_Crostini) {
+  base::FilePath path("/media/fuse/crostini-abcdef/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kLaunch));
+  notifier().NotifyFileTasks({CreateFileSystemUrl(path)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_UnknownPath) {
+  base::FilePath path("/some/other/path");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileTasks({CreateFileSystemUrl(path)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_RemovableMedia) {
+  base::FilePath path("/media/removable/device/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileTasks({CreateFileSystemUrl(path)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_LegacyDrive) {
+  base::FilePath path("/special/drive/root/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileTasks(
+      {CreateFileSystemUrl(path, storage::kFileSystemTypeDrive)});
+}
+
+TEST_F(FileTasksNotifierTest, FileTask_Multiple) {
+  base::FilePath local_path = profile().GetPath().Append("file");
+  base::FilePath drivefs_path("/media/fuse/drivefs-abcedf/root/file");
+  base::FilePath arc_path("/run/arc/sdcard/write/emulated/0/file");
+  base::FilePath crostini_path("/media/fuse/crostini-abcdef/file");
+  base::FilePath unknown_path("/some/other/path");
+  base::FilePath removable_path("/media/removable/device/file");
+  base::FilePath legacy_drive_path("/special/drive/root/file");
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(local_path, FileTasksObserver::OpenType::kLaunch));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(drivefs_path, FileTasksObserver::OpenType::kLaunch));
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(
+                              arc_path, FileTasksObserver::OpenType::kLaunch));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(crostini_path, FileTasksObserver::OpenType::kLaunch));
+  notifier().NotifyFileTasks({
+      CreateFileSystemUrl(local_path),
+      CreateFileSystemUrl(drivefs_path, storage::kFileSystemTypeDriveFs),
+      CreateFileSystemUrl(arc_path),
+      CreateFileSystemUrl(crostini_path),
+      CreateFileSystemUrl(unknown_path),
+      CreateFileSystemUrl(removable_path),
+      CreateFileSystemUrl(legacy_drive_path),
+  });
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_Local) {
+  base::FilePath path = profile().GetPath().Append("file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kOpen));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kSaveAs));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_DriveFs) {
+  base::FilePath path("/media/fuse/drivefs-abcdef/root/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kOpen));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kSaveAs));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_Arc) {
+  base::FilePath path("/run/arc/sdcard/write/emulated/0/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kOpen));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kSaveAs));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_Crostini) {
+  base::FilePath path("/media/fuse/crostini-abcdef/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kOpen));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kSaveAs));
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_UnknownPath) {
+  base::FilePath path("/some/other/path");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_RemovableMedia) {
+  base::FilePath path("/media/removable/device/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, true);
+  notifier().NotifyFileDialogSelection({CreateSelectedFileInfo(path)}, false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_LegacyDrive) {
+  base::FilePath path("/special/drive/root/file");
+  base::FilePath local_path =
+      profile().GetPath().Append("GCache/v1/files/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().NotifyFileDialogSelection(
+      {CreateSelectedFileInfo(path, local_path), CreateSelectedFileInfo(path)},
+      true);
+  notifier().NotifyFileDialogSelection(
+      {CreateSelectedFileInfo(path, local_path), CreateSelectedFileInfo(path)},
+      false);
+}
+
+TEST_F(FileTasksNotifierTest, DialogSelection_Multiple) {
+  base::FilePath local_path = profile().GetPath().Append("file");
+  base::FilePath drivefs_path("/media/fuse/drivefs-abcdef/root/file");
+  base::FilePath arc_path("/run/arc/sdcard/write/emulated/0/file");
+  base::FilePath crostini_path("/media/fuse/crostini-abcdef/file");
+  base::FilePath unknown_path("/some/other/path");
+  base::FilePath removable_path("/media/removable/device/file");
+  base::FilePath legacy_drive_path("/special/drive/root/file");
+  base::FilePath legacy_drive_local_path =
+      profile().GetPath().Append("GCache/v1/files/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(
+                              local_path, FileTasksObserver::OpenType::kOpen));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(drivefs_path, FileTasksObserver::OpenType::kOpen));
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(arc_path, FileTasksObserver::OpenType::kOpen));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(crostini_path, FileTasksObserver::OpenType::kOpen));
+
+  notifier().NotifyFileDialogSelection(
+      {CreateSelectedFileInfo(local_path), CreateSelectedFileInfo(drivefs_path),
+       CreateSelectedFileInfo(arc_path), CreateSelectedFileInfo(crostini_path),
+       CreateSelectedFileInfo(unknown_path),
+       CreateSelectedFileInfo(legacy_drive_path, legacy_drive_local_path),
+       CreateSelectedFileInfo(legacy_drive_path),
+       CreateSelectedFileInfo(removable_path)},
+      true);
+
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(local_path, FileTasksObserver::OpenType::kSaveAs));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(drivefs_path, FileTasksObserver::OpenType::kSaveAs));
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(
+                              arc_path, FileTasksObserver::OpenType::kSaveAs));
+  EXPECT_CALL(
+      observer(),
+      OnFilesOpenedImpl(crostini_path, FileTasksObserver::OpenType::kSaveAs));
+
+  notifier().NotifyFileDialogSelection(
+      {CreateSelectedFileInfo(local_path), CreateSelectedFileInfo(drivefs_path),
+       CreateSelectedFileInfo(arc_path), CreateSelectedFileInfo(crostini_path),
+       CreateSelectedFileInfo(unknown_path),
+       CreateSelectedFileInfo(legacy_drive_path, legacy_drive_local_path),
+       CreateSelectedFileInfo(legacy_drive_path),
+       CreateSelectedFileInfo(removable_path)},
+      false);
+}
+
+TEST_F(FileTasksNotifierTest, Download_Local) {
+  base::FilePath path = profile().GetPath().Append("file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kDownload));
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_DriveFs) {
+  base::FilePath path("/media/fuse/drivefs/root/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kDownload));
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_Arc) {
+  base::FilePath path("/run/arc/sdcard/write/emulated/0/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kDownload));
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_Crostini) {
+  base::FilePath path("/media/fuse/crostini-abcdef/file");
+  EXPECT_CALL(observer(),
+              OnFilesOpenedImpl(path, FileTasksObserver::OpenType::kDownload));
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_UnknownPath) {
+  base::FilePath path("/some/other/path");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_RemovableMedia) {
+  base::FilePath path("/media/removable/device/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_LegacyDrive) {
+  base::FilePath path("/special/drive/root/file");
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  notifier().OnDownloadUpdated(nullptr, CreateCompletedDownloadItem(path));
+}
+
+TEST_F(FileTasksNotifierTest, Download_Incomplete) {
+  EXPECT_CALL(observer(), OnFilesOpenedImpl(_, _)).Times(0);
+  content::FakeDownloadItem download_item;
+  download_item.SetTargetFilePath(profile().GetPath().Append("file"));
+
+  for (auto state : {download::DownloadItem::DownloadState::IN_PROGRESS,
+                     download::DownloadItem::DownloadState::CANCELLED,
+                     download::DownloadItem::DownloadState::INTERRUPTED}) {
+    download_item.SetState(state);
+    notifier().OnDownloadUpdated(nullptr, &download_item);
+  }
+}
+
+}  // namespace
+}  // namespace file_tasks
+}  // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_observer.h b/chrome/browser/chromeos/file_manager/file_tasks_observer.h
new file mode 100644
index 0000000..8a05970
--- /dev/null
+++ b/chrome/browser/chromeos/file_manager/file_tasks_observer.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_OBSERVER_H_
+#define CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_OBSERVER_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/observer_list_types.h"
+
+namespace file_manager {
+namespace file_tasks {
+
+class FileTasksObserver : public base::CheckedObserver {
+ public:
+  enum class OpenType {
+    // Launch from the files app or the Downloads page.
+    kLaunch,
+
+    // Chosen from a file-open dialog.
+    kOpen,
+
+    // Chosen from a file-save-as dialog.
+    kSaveAs,
+
+    // A file was downloaded in Chrome.
+    kDownload,
+  };
+
+  struct FileOpenEvent {
+    base::FilePath path;
+    OpenType open_type;
+  };
+
+  virtual void OnFilesOpened(const std::vector<FileOpenEvent>& file_opens) {}
+};
+
+}  // namespace file_tasks
+}  // namespace file_manager
+
+#endif  // CHROME_BROWSER_CHROMEOS_FILE_MANAGER_FILE_TASKS_OBSERVER_H_
diff --git a/chrome/browser/chromeos/file_manager/path_util.cc b/chrome/browser/chromeos/file_manager/path_util.cc
index 46fb66c0..6de8323 100644
--- a/chrome/browser/chromeos/file_manager/path_util.cc
+++ b/chrome/browser/chromeos/file_manager/path_util.cc
@@ -366,9 +366,7 @@
       GetDownloadsFolderForProfile(primary_profile);
   base::FilePath result_path(kArcDownloadRoot);
   if (primary_downloads.AppendRelativePath(path, &result_path)) {
-    // TODO(niwa): Switch to using kFileSystemFileproviderUrl once we completely
-    // move FileProvider to arc.file_system (b/111816608).
-    *arc_url_out = GURL(arc::kIntentHelperFileproviderUrl)
+    *arc_url_out = GURL(arc::kFileSystemFileproviderUrl)
                        .Resolve(net::EscapePath(result_path.AsUTF8Unsafe()));
     return true;
   }
@@ -377,8 +375,7 @@
   result_path = base::FilePath(kArcExternalFilesRoot);
   if (base::FilePath(kAndroidFilesPath)
           .AppendRelativePath(path, &result_path)) {
-    // TODO(niwa): Switch to using kFileSystemFileproviderUrl.
-    *arc_url_out = GURL(arc::kIntentHelperFileproviderUrl)
+    *arc_url_out = GURL(arc::kFileSystemFileproviderUrl)
                        .Resolve(net::EscapePath(result_path.AsUTF8Unsafe()));
     return true;
   }
diff --git a/chrome/browser/chromeos/file_manager/path_util_unittest.cc b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
index 119a8c35..668c6091 100644
--- a/chrome/browser/chromeos/file_manager/path_util_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
@@ -781,7 +781,7 @@
       chromeos::ProfileHelper::Get()->GetProfileByUserIdHashForTest(
           "user@gmail.com-hash"));
   EXPECT_TRUE(ConvertPathToArcUrl(downloads.AppendASCII("a/b/c"), &url));
-  EXPECT_EQ(GURL("content://org.chromium.arc.intent_helper.fileprovider/"
+  EXPECT_EQ(GURL("content://org.chromium.arc.file_system.fileprovider/"
                  "download/a/b/c"),
             url);
 }
@@ -906,7 +906,7 @@
             run_loop->Quit();
             ASSERT_EQ(1U, urls.size());
             EXPECT_EQ(
-                GURL("content://org.chromium.arc.intent_helper.fileprovider/"
+                GURL("content://org.chromium.arc.file_system.fileprovider/"
                      "download/a/b/c"),
                 urls[0]);
           },
@@ -1015,7 +1015,7 @@
             run_loop->Quit();
             ASSERT_EQ(1U, urls.size());
             EXPECT_EQ(
-                GURL("content://org.chromium.arc.intent_helper.fileprovider/"
+                GURL("content://org.chromium.arc.file_system.fileprovider/"
                      "external_files/Pictures/a/b.jpg"),
                 urls[0]);
           },
@@ -1061,7 +1061,7 @@
                            "2Fb%2Fc"),
                       urls[2]);
             EXPECT_EQ(
-                GURL("content://org.chromium.arc.intent_helper.fileprovider/"
+                GURL("content://org.chromium.arc.file_system.fileprovider/"
                      "external_files/a/b/c"),
                 urls[3]);
           },
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
index c89b2de..3cb31258 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.cc
@@ -5,11 +5,14 @@
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 
 #include <string>
+#include <utility>
 
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/debug_daemon_client.h"
 #include "components/exo/shell_surface_util.h"
 #include "components/prefs/pref_service.h"
 
@@ -73,6 +76,36 @@
   return false;
 }
 
+void OnPluginVmDispatcherStarted(Profile* profile,
+                                 PluginVmStartedCallback callback,
+                                 bool success) {
+  if (!success) {
+    LOG(ERROR) << "Failed to start PluginVm dispatcher service";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  // TODO(https://crbug.com/904853): Send dbus call to dispatcher to start
+  // PluginVm.
+  std::move(callback).Run(false);
+}
+
+void StartPluginVmForProfile(Profile* profile,
+                             PluginVmStartedCallback callback) {
+  // Defensive check to prevent starting PluginVm when it is not allowed.
+  if (!IsPluginVmAllowedForProfile(profile)) {
+    LOG(ERROR) << "Attempt to start PluginVm when it is not allowed";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  VLOG(1) << "Starting PluginVm dispatcher service";
+  chromeos::DBusThreadManager::Get()
+      ->GetDebugDaemonClient()
+      ->StartPluginVmDispatcher(base::BindOnce(&OnPluginVmDispatcherStarted,
+                                               profile, std::move(callback)));
+}
+
 std::string GetPluginVmLicenseKey() {
   std::string plugin_vm_license_key;
   if (!chromeos::CrosSettings::Get()->GetString(chromeos::kPluginVmLicenseKey,
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
index b041e33..a449b7f 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_util.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "base/callback.h"
+
 namespace aura {
 class Window;
 }  // namespace aura
@@ -15,6 +17,8 @@
 
 namespace plugin_vm {
 
+using PluginVmStartedCallback = base::OnceCallback<void(bool)>;
+
 // Generated as crx_file::id_util::GenerateId("org.chromium.plugin_vm");
 constexpr char kPluginVmAppId[] = "lgjpclljbbmphhnalkeplcmnjpfmmaek";
 
@@ -35,6 +39,9 @@
 // Checks if an window is for plugin vm.
 bool IsPluginVmWindow(const aura::Window* window);
 
+void StartPluginVmForProfile(Profile* profile,
+                             PluginVmStartedCallback callback);
+
 // Retrieves the license key to be used for PluginVm. If
 // none is set this will return an empty string.
 std::string GetPluginVmLicenseKey();
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_util_unittest.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_util_unittest.cc
index 2eba44f..97a60f30 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_util_unittest.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_util_unittest.cc
@@ -5,12 +5,15 @@
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 
 #include "base/json/json_reader.h"
+#include "base/test/mock_callback.h"
 #include "chrome/browser/chromeos/login/users/mock_user_manager.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_debug_daemon_client.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -26,6 +29,12 @@
     settings_helper_.ReplaceDeviceSettingsProviderWithStub();
     profile_builder.SetProfileName("user0");
     testing_profile_ = profile_builder.Build();
+
+    std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter =
+        chromeos::DBusThreadManager::GetSetterForTesting();
+    dbus_setter->SetDebugDaemonClient(
+        std::unique_ptr<chromeos::DebugDaemonClient>(
+            new chromeos::FakeDebugDaemonClient));
   }
 
   void TearDown() override {
@@ -38,6 +47,39 @@
   chromeos::ScopedCrosSettingsTestHelper settings_helper_;
   std::unique_ptr<TestingProfile> testing_profile_;
   content::TestBrowserThreadBundle thread_bundle_;
+  chromeos::MockUserManager user_manager_;
+
+  void SetPolicyRequirementsToAllowPluginVm() {
+    settings_helper_.SetBoolean(chromeos::kPluginVmAllowed, true);
+    EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
+
+    settings_helper_.SetString(chromeos::kPluginVmLicenseKey, "LICENSE_KEY");
+    EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
+
+    testing_profile_->GetPrefs()->Set(plugin_vm::prefs::kPluginVmImage,
+                                      *base::JSONReader::ReadDeprecated(R"(
+      {
+          "url": "https://example.com/plugin_vm_image",
+          "hash": "842841a4c75a55ad050d686f4ea5f77e83ae059877fe9b6946aa63d3d057ed32"
+      }
+    )"));
+  }
+
+  void SetUserRequirementsToAllowPluginVm() {
+    // User for the profile should be affiliated with the device.
+    const AccountId account_id(AccountId::FromUserEmailGaiaId(
+        testing_profile_->GetProfileUserName(), "id"));
+    user_manager_.AddUserWithAffiliationAndType(
+        account_id, true, user_manager::USER_TYPE_REGULAR);
+    chromeos::ProfileHelper::Get()->SetProfileToUserMappingForTesting(
+        user_manager_.GetActiveUser());
+  }
+
+  void AllowPluginVm() {
+    SetPolicyRequirementsToAllowPluginVm();
+    EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
+    SetUserRequirementsToAllowPluginVm();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(PluginVmUtilTest);
@@ -47,33 +89,8 @@
        IsPluginVmAllowedForProfileReturnsTrueOnceAllConditionsAreMet) {
   EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
 
-  // Set the requirements for the user: the user for the profile is
-  // affiliated with the device.
-  chromeos::MockUserManager user_manager_;
-  const AccountId account_id_(AccountId::FromUserEmailGaiaId(
-      testing_profile_->GetProfileUserName(), "id"));
-  user_manager_.AddUserWithAffiliationAndType(account_id_, true,
-                                              user_manager::USER_TYPE_REGULAR);
-  user_manager::User* const user = user_manager_.GetActiveUser();
-  chromeos::ProfileHelper::Get()->SetProfileToUserMappingForTesting(user);
-  EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
+  AllowPluginVm();
 
-  // Set all policy requirements: PluginVm is allowed to run on the device,
-  // and all other policies required for correct configuration (license, image)
-  // are set.
-  settings_helper_.SetBoolean(chromeos::kPluginVmAllowed, true);
-  EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
-
-  settings_helper_.SetString(chromeos::kPluginVmLicenseKey, "LICENSE_KEY");
-  EXPECT_FALSE(IsPluginVmAllowedForProfile(testing_profile_.get()));
-
-  testing_profile_->GetPrefs()->Set(plugin_vm::prefs::kPluginVmImage,
-                                    *base::JSONReader::ReadDeprecated(R"(
-    {
-        "url": "https://example.com/plugin_vm_image",
-        "hash": "842841a4c75a55ad050d686f4ea5f77e83ae059877fe9b6946aa63d3d057ed32"
-    }
-    )"));
   EXPECT_TRUE(IsPluginVmAllowedForProfile(testing_profile_.get()));
 }
 
@@ -87,6 +104,22 @@
   EXPECT_TRUE(IsPluginVmConfigured(testing_profile_.get()));
 }
 
+TEST_F(PluginVmUtilTest, PluginVmNotStartedIfNotAllowed) {
+  base::MockCallback<PluginVmStartedCallback> callback;
+  EXPECT_CALL(callback, Run(false));
+  StartPluginVmForProfile(testing_profile_.get(), callback.Get());
+  thread_bundle_.RunUntilIdle();
+}
+
+TEST_F(PluginVmUtilTest, PluginVmNotStarted) {
+  AllowPluginVm();
+
+  base::MockCallback<PluginVmStartedCallback> callback;
+  EXPECT_CALL(callback, Run(false));
+  StartPluginVmForProfile(testing_profile_.get(), callback.Get());
+  thread_bundle_.RunUntilIdle();
+}
+
 TEST_F(PluginVmUtilTest, GetPluginVmLicenseKey) {
   // If no license key is set, the method should return the empty string.
   EXPECT_EQ(std::string(), GetPluginVmLicenseKey());
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 3daa254..c34834d 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -1455,6 +1455,15 @@
   ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
   VerifyPreviewLoaded();
 
+  PreviewsUITabHelper* ui_tab_helper =
+      PreviewsUITabHelper::FromWebContents(GetWebContents());
+  previews::PreviewsUserData* previews_data =
+      ui_tab_helper->previews_user_data();
+  // Grab the page id and session now because they may change after the reload.
+  uint64_t expected_page_id = previews_data->server_lite_page_info()->page_id;
+  std::string expected_session_key =
+      previews_data->server_lite_page_info()->drp_session_key;
+
   // Starting a new page load will send a pingback for the previous page load.
   GetWebContents()->GetController().Reload(content::ReloadType::NORMAL, false);
   pingback_client->WaitForPingback();
@@ -1470,20 +1479,8 @@
   if (GetParam())
     return;
 
-  PreviewsUITabHelper* ui_tab_helper =
-      PreviewsUITabHelper::FromWebContents(GetWebContents());
-  previews::PreviewsUserData* previews_data =
-      ui_tab_helper->previews_user_data();
-
-  EXPECT_EQ(data->session_key(),
-            previews_data->server_lite_page_info()->drp_session_key);
-
-  // TODO(crbug.com/952523): The page id is being incremented for every
-  // restarted navigation. Fix and remove the early return.
-  return;
-
-  EXPECT_EQ(data->page_id().value(),
-            previews_data->server_lite_page_info()->page_id);
+  EXPECT_EQ(data->session_key(), expected_session_key);
+  EXPECT_EQ(data->page_id().value(), expected_page_id);
 }
 
 class PreviewsLitePageServerTimeoutBrowserTest
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
index 7bdacbf..0841b60d 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle.cc
@@ -116,26 +116,27 @@
     // restart. Note: This could be a navigation to the litepages server, or to
     // the original URL.
     if (restarted_navigation_url_ == handle->GetURL()) {
-      // Get a new page id.
-      PreviewsService* previews_service = PreviewsServiceFactory::GetForProfile(
-          Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
-      PreviewsLitePageNavigationThrottleManager* manager =
-          previews_service->previews_lite_page_decider();
-      uint64_t page_id = manager->GeneratePageID();
+      if (!info_) {
+        // Create a new info_ if needed. This will use the previous page_id,
+        // which is desired.
+        PreviewsService* previews_service =
+            PreviewsServiceFactory::GetForProfile(Profile::FromBrowserContext(
+                web_contents()->GetBrowserContext()));
+        PreviewsLitePageNavigationThrottleManager* manager =
+            previews_service->previews_lite_page_decider();
+
+        info_ =
+            PreviewsLitePageNavigationThrottle::GetOrCreateServerLitePageInfo(
+                handle, manager)
+                ->Clone();
+      }
 
       // Create a new PreviewsUserData if needed.
       PreviewsUITabHelper* ui_tab_helper =
           PreviewsUITabHelper::FromWebContents(web_contents());
       previews::PreviewsUserData* previews_data =
-          ui_tab_helper->CreatePreviewsUserDataForNavigationHandle(handle,
-                                                                   page_id);
-
-      // Set the lite page state on the user data.
-      if (!info_) {
-        info_ =
-            std::make_unique<previews::PreviewsUserData::ServerLitePageInfo>();
-        info_->original_navigation_start = handle->NavigationStart();
-      }
+          ui_tab_helper->CreatePreviewsUserDataForNavigationHandle(
+              handle, info_->page_id);
       previews_data->set_server_lite_page_info(std::move(info_));
 
       // Reset member state.
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index 4b9337c..8899822 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -1562,7 +1562,7 @@
   EXPECT_TRUE(IsTabDiscarded(browser4->tab_strip_model()->GetWebContentsAt(1)));
 }
 
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_LINUX)
 // Flaky: https://crbug.com/918701
 #define MAYBE_UnfreezeTabOnNavigationEvent DISABLED_UnfreezeTabOnNavigationEvent
 #else
diff --git a/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc b/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
index 29ed8b5..694a0972 100644
--- a/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_passwords_sync_test.cc
@@ -227,10 +227,17 @@
   ASSERT_EQ(2, GetPasswordCount(0));
 }
 
+// TODO(https://crbug.com/952074): re-enable once flakiness is addressed.
+#if defined(THREAD_SANITIZER)
+#define MAYBE_ExerciseUssMigrator DISABLED_ExerciseUssMigrator
+#else
+#define MAYBE_ExerciseUssMigrator ExerciseUssMigrator
+#endif
+
 // Now that local passwords, the local sync directory and the sever are
 // populated with two passwords, USS is enabled for passwords.
 IN_PROC_BROWSER_TEST_F(SingleClientPasswordsSyncUssMigratorTest,
-                       ExerciseUssMigrator) {
+                       MAYBE_ExerciseUssMigrator) {
   base::test::ScopedFeatureList override_features;
   override_features.InitAndEnableFeature(switches::kSyncUSSPasswords);
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 6e30a1b..643059b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2971,6 +2971,7 @@
       "//components/constrained_window",
       "//components/payments/content",
       "//components/payments/core",
+      "//components/ui_devtools/views",
       "//device/vr/buildflags:buildflags",
       "//services/ws/public/cpp/input_devices",
       "//ui/views:buildflags",
@@ -3050,7 +3051,6 @@
 
     if (use_aura) {
       deps += [
-        "//components/ui_devtools/views",
         "//services/ws/public/cpp",
         "//services/ws/public/mojom",
       ]
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.cc b/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.cc
index d231352..2eb5cc41 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.cc
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.cc
@@ -28,16 +28,22 @@
 ArcAppShortcutSearchResult::ArcAppShortcutSearchResult(
     arc::mojom::AppShortcutItemPtr data,
     Profile* profile,
-    AppListControllerDelegate* list_controller)
+    AppListControllerDelegate* list_controller,
+    bool is_recommendation)
     : data_(std::move(data)),
       profile_(profile),
       list_controller_(list_controller) {
   SetTitle(base::UTF8ToUTF16(data_->short_label));
   set_id(kAppShortcutSearchPrefix + GetAppId() + "/" + data_->shortcut_id);
-  SetDisplayType(ash::SearchResultDisplayType::kTile);
   SetAccessibleName(ComputeAccessibleName());
   SetResultType(ash::SearchResultType::kArcAppShortcut);
 
+  if (is_recommendation) {
+    SetDisplayType(ash::SearchResultDisplayType::kRecommendation);
+  } else {
+    SetDisplayType(ash::SearchResultDisplayType::kTile);
+  }
+
   const int icon_dimension =
       app_list::AppListConfig::instance().search_tile_icon_dimension();
   icon_decode_request_ = std::make_unique<arc::IconDecodeRequest>(
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.h b/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.h
index b946333..7e7a26d3 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.h
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_shortcut_search_result.h
@@ -28,9 +28,12 @@
 class ArcAppShortcutSearchResult : public ChromeSearchResult,
                                    public AppIconLoaderDelegate {
  public:
+  // Constructor for ArcAppShortcutSearchResult. |is_recommendation|
+  // defines the display type of search results.
   ArcAppShortcutSearchResult(arc::mojom::AppShortcutItemPtr data,
                              Profile* profile,
-                             AppListControllerDelegate* list_controller);
+                             AppListControllerDelegate* list_controller,
+                             bool is_recommendation);
   ~ArcAppShortcutSearchResult() override;
 
   // ChromeSearchResult:
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.cc b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.cc
index 9ddcb515..a6905c6 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.cc
@@ -30,6 +30,20 @@
 // multiplied by this amount.
 constexpr float kDefaultRankerScoreCoefficient = 0.1f;
 
+// Linearly maps |score| to the range [min, max].
+// |score| is assumed to be within [0.0, 1.0]; if it's greater than 1.0
+// then max is returned; if it's less than 0.0, then min is returned.
+// |min| and |max| are assumed to be within [0.0, 1.0].
+float ReRange(const float score, const float min, const float max) {
+  DCHECK_LT(min, max);
+  if (score >= 1.0f)
+    return max;
+  if (score <= 0.0f)
+    return min;
+
+  return min + score * (max - min);
+}
+
 }  // namespace
 
 namespace app_list {
@@ -55,18 +69,37 @@
                 GetAppShortcutGlobalQueryItems)
           : nullptr;
 
-  if (!app_instance || query.empty()) {
+  if (!app_instance) {
     ClearResults();
     return;
   }
 
   // Invalidate the weak ptr to prevent previous callback run.
   weak_ptr_factory_.InvalidateWeakPtrs();
-  app_instance->GetAppShortcutGlobalQueryItems(
-      base::UTF16ToUTF8(query), max_results_,
-      base::BindOnce(
-          &ArcAppShortcutsSearchProvider::OnGetAppShortcutGlobalQueryItems,
-          weak_ptr_factory_.GetWeakPtr()));
+  int max_items = max_results_;
+  if (query.empty()) {
+    if (ShouldRerankZeroState()) {
+      max_items = base::GetFieldTrialParamByFeatureAsInt(
+          app_list_features::kEnableZeroStateAppsRanker, "max_items_to_get",
+          10);
+      app_instance->GetAppShortcutGlobalQueryItems(
+          base::UTF16ToUTF8(query), max_items,
+          base::BindOnce(
+              &ArcAppShortcutsSearchProvider::UpdateRecomendedResults,
+              weak_ptr_factory_.GetWeakPtr()));
+    }
+  } else {
+    if (ShouldReRankQueryBased()) {
+      max_items = base::GetFieldTrialParamByFeatureAsInt(
+          app_list_features::kEnableQueryBasedAppsRanker, "max_items_to_get",
+          10);
+    }
+    app_instance->GetAppShortcutGlobalQueryItems(
+        base::UTF16ToUTF8(query), max_items,
+        base::BindOnce(
+            &ArcAppShortcutsSearchProvider::OnGetAppShortcutGlobalQueryItems,
+            weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 void ArcAppShortcutsSearchProvider::Train(const std::string& id,
@@ -75,27 +108,74 @@
     ranker_->Train(id);
 }
 
-void ArcAppShortcutsSearchProvider::OnGetAppShortcutGlobalQueryItems(
+bool ArcAppShortcutsSearchProvider::ShouldRerankZeroState() const {
+  return app_list_features::IsZeroStateAppsRankerEnabled() &&
+         base::GetFieldTrialParamByFeatureAsBool(
+             app_list_features::kEnableZeroStateAppsRanker,
+             "rank_arc_app_shortcuts", false) &&
+         ranker_ != nullptr;
+}
+
+bool ArcAppShortcutsSearchProvider::ShouldReRankQueryBased() const {
+  return app_list_features::IsQueryBasedAppsRankerEnabled() &&
+         base::GetFieldTrialParamByFeatureAsBool(
+             app_list_features::kEnableQueryBasedAppsRanker,
+             "rank_arc_app_shortcuts", false) &&
+         ranker_ != nullptr;
+}
+
+void ArcAppShortcutsSearchProvider::UpdateRecomendedResults(
     std::vector<arc::mojom::AppShortcutItemPtr> shortcut_items) {
   const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile_);
   DCHECK(arc_prefs);
 
-  // To rerank shortcuts, both the main Roselle feature flag and the
-  // |rank_arc_app_shortcuts| parameter must be enabled. If so, then shortcuts
-  // are reranked for both zero-state and query-based results.
-  // TODO(crbug.com/931149): once zero-state arc shortcut results are
-  // implemented, split the |rank_arc_app_shortcuts| flag into two flags to
-  // control zero-state and query-based re-ranking individually.
-  const bool should_rerank =
-      app_list_features::IsQueryBasedAppsRankerEnabled() &&
-      base::GetFieldTrialParamByFeatureAsBool(
-          app_list_features::kEnableQueryBasedAppsRanker,
-          "rank_arc_app_shortcuts", false) &&
-      ranker_ != nullptr;
+  // Maps app IDs to their score according to |ranker_|
+  base::flat_map<std::string, float> ranker_scores;
+  SearchProvider::Results search_results;
+  for (auto& item : shortcut_items) {
+    const std::string app_id =
+        arc_prefs->GetAppIdByPackageName(item->package_name.value());
+    std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
+        arc_prefs->GetApp(app_id);
+    // Ignore shortcuts for apps that are not present in the launcher.
+    if (!app_info || !app_info->show_in_launcher)
+      continue;
+    auto result = std::make_unique<ArcAppShortcutSearchResult>(
+        std::move(item), profile_, list_controller_,
+        true /*is_recommendation*/);
+    DCHECK(ShouldRerankZeroState());
+    ranker_scores = ranker_->Rank();
+    const auto find_in_ranker = ranker_scores.find(result->id());
+    // TODO(crbug.com/931149): This logic mimics the logic of
+    // AppSearchProvider::UpdateRecommendedResults. Need update to a
+    // logic that fits ArcAppShortcut.
+    if (find_in_ranker != ranker_scores.end()) {
+      // Case 1: If it is recommended by |ranker_|, set the relevance as
+      // a score in a range [0.67, 1.0].
+      result->set_relevance(ReRange(find_in_ranker->second, 0.67, 1.0));
+    } else if (!app_info->install_time.is_null() ||
+               !app_info->last_launch_time.is_null()) {
+      // Case 2: It it has |install_time| or |last_launch_time|, set the
+      // relevance to 0.5.
+      result->set_relevance(0.5);
+    } else {
+      // Case 3: otherwise set relevance to 0.0.
+      result->set_relevance(0);
+    }
+    search_results.emplace_back(std::move(result));
+  }
+  SwapResults(&search_results);
+}
+
+void ArcAppShortcutsSearchProvider::OnGetAppShortcutGlobalQueryItems(
+    std::vector<arc::mojom::AppShortcutItemPtr> shortcut_items) {
+  const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile_);
+  DCHECK(arc_prefs);
   // Maps app IDs to their score according to |ranker_|.
   base::flat_map<std::string, float> ranker_scores;
   float ranker_score_coefficient = kDefaultRankerScoreCoefficient;
   float ranker_score_boost = kDefaultRankerScoreBoost;
+  const bool should_rerank = ShouldReRankQueryBased();
   if (should_rerank) {
     ranker_scores = ranker_->Rank();
     ranker_score_coefficient = base::GetFieldTrialParamByFeatureAsDouble(
@@ -116,7 +196,8 @@
     if (!app_info || !app_info->show_in_launcher)
       continue;
     auto result = std::make_unique<ArcAppShortcutSearchResult>(
-        std::move(item), profile_, list_controller_);
+        std::move(item), profile_, list_controller_,
+        false /*is_recommendation*/);
     if (should_rerank) {
       const auto find_in_ranker = ranker_scores.find(result->id());
       if (find_in_ranker != ranker_scores.end()) {
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.h b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.h
index 8b3b3914..548b38f 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.h
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.h
@@ -34,8 +34,14 @@
   void Train(const std::string& id, RankingItemType type) override;
 
  private:
+  // QueryBased: filter and rerank results for non-empty queries
   void OnGetAppShortcutGlobalQueryItems(
       std::vector<arc::mojom::AppShortcutItemPtr> shortcut_items);
+  // ZeroState: filter and rerank results for empty queries
+  void UpdateRecomendedResults(
+      std::vector<arc::mojom::AppShortcutItemPtr> shortcut_items);
+  bool ShouldRerankZeroState() const;
+  bool ShouldReRankQueryBased() const;
 
   const int max_results_;
   Profile* const profile_;                            // Owned by ProfileInfo.
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
index 8aebef2..6eadf9d 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider_unittest.cc
@@ -52,17 +52,16 @@
     AppListTestBase::TearDown();
   }
 
-  void CreateRanker(const std::map<std::string, std::string>& params = {}) {
+  void CreateRanker(const base::Feature& feature,
+                    const std::map<std::string, std::string>& params = {}) {
     if (!params.empty()) {
-      scoped_feature_list_.InitAndEnableFeatureWithParameters(
-          app_list_features::kEnableQueryBasedAppsRanker, params);
+      scoped_feature_list_.InitAndEnableFeatureWithParameters(feature, params);
       ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
       ranker_ =
           std::make_unique<AppSearchResultRanker>(temp_dir_.GetPath(),
                                                   /*is_ephemeral_user=*/false);
     } else {
-      scoped_feature_list_.InitWithFeatures(
-          {}, {app_list_features::kEnableQueryBasedAppsRanker});
+      scoped_feature_list_.InitWithFeatures({}, {feature});
     }
   }
 
@@ -103,7 +102,7 @@
 };
 
 TEST_P(ArcAppShortcutsSearchProviderTest, Basic) {
-  CreateRanker();
+  CreateRanker(app_list_features::kEnableZeroStateAppsRanker);
   EXPECT_EQ(ranker_, nullptr);
   const bool launchable = GetParam();
 
@@ -131,7 +130,7 @@
 }
 
 TEST_F(ArcAppShortcutsSearchProviderTest, RankerIsDisableWithFlag) {
-  CreateRanker();
+  CreateRanker(app_list_features::kEnableQueryBasedAppsRanker);
   EXPECT_EQ(ranker_, nullptr);
 
   const std::string app_id = AddArcAppAndShortcut(
@@ -161,12 +160,13 @@
 }
 
 TEST_F(ArcAppShortcutsSearchProviderTest, RankerImproveScores) {
-  CreateRanker({{"rank_arc_app_shortcuts", "true"}});
+  CreateRanker(app_list_features::kEnableQueryBasedAppsRanker,
+               {{"rank_arc_app_shortcuts", "true"}, {"max_items_to_get", "6"}});
   EXPECT_NE(ranker_, nullptr);
 
   const std::string app_id = AddArcAppAndShortcut(
       CreateAppInfo("FakeName", "FakeActivity", kFakeAppPackageName), true);
-  const size_t kMaxResults = 4;
+  const size_t kMaxResults = 6;
   constexpr char kQuery[] = "shortlabel";
   constexpr char kPrefix[] = "appshortcutsearch://";
   constexpr char kShortcutId[] = "/ShortcutId ";
@@ -188,6 +188,39 @@
     EXPECT_GT(result->relevance(), 0);
 }
 
+TEST_F(ArcAppShortcutsSearchProviderTest, EmptyQueries) {
+  CreateRanker(app_list_features::kEnableZeroStateAppsRanker,
+               {{"rank_arc_app_shortcuts", "true"}, {"max_items_to_get", "6"}});
+  EXPECT_NE(ranker_, nullptr);
+
+  const std::string app_id = AddArcAppAndShortcut(
+      CreateAppInfo("FakeName", "FakeActivity", kFakeAppPackageName), true);
+  const size_t kMaxResults = 6;
+  constexpr char kQuery[] = "";
+  constexpr char kPrefix[] = "appshortcutsearch://";
+  constexpr char kShortcutId[] = "/ShortcutId ";
+
+  // Create a search provider
+  auto provider = std::make_unique<ArcAppShortcutsSearchProvider>(
+      kMaxResults, profile(), controller_.get(), ranker_.get());
+  arc::IconDecodeRequest::DisableSafeDecodingForTesting();
+
+  for (size_t i = 0; i < kMaxResults; i++) {
+    provider->Train(
+        base::StrCat({kPrefix, app_id, kShortcutId, base::NumberToString(i)}),
+        RankingItemType::kArcAppShortcut);
+  }
+  provider->Start(base::UTF8ToUTF16(kQuery));
+  // Verify search results.
+  const auto& results = provider->results();
+  EXPECT_EQ(results.size(), kMaxResults);
+  for (const auto& result : results) {
+    EXPECT_EQ(ash::SearchResultDisplayType::kRecommendation,
+              result->display_type());
+    EXPECT_GT(result->relevance(), 0);
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(, ArcAppShortcutsSearchProviderTest, testing::Bool());
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
index 97bef53..f836c2e5a0 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
@@ -8,20 +8,20 @@
 
 #include "base/command_line.h"
 #include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/ui/views/chrome_constrained_window_views_client.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_views_delegate.h"
 #include "chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller.h"
 #include "components/constrained_window/constrained_window_views.h"
+#include "components/ui_devtools/switches.h"
+#include "components/ui_devtools/views/devtools_server_util.h"
 #include "services/service_manager/sandbox/switches.h"
 #include "ui/base/material_design/material_design_controller.h"
 
 #if defined(USE_AURA)
 #include "base/run_loop.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/net/system_network_context_manager.h"
-#include "components/ui_devtools/switches.h"
-#include "components/ui_devtools/views/devtools_server_util.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
@@ -81,15 +81,13 @@
 }
 
 void ChromeBrowserMainExtraPartsViews::PreProfileInit() {
-#if defined(USE_AURA)
   if (ui_devtools::UiDevToolsServer::IsUiDevToolsEnabled(
           ui_devtools::switches::kEnableUiDevTools)) {
-    // Start the UI Devtools server using Chrome's local aura::Env instance.
+    // Starts the UI Devtools server for the browser UI. On Aura platforms,
     // ChromeBrowserMainExtraPartsAsh wires up Ash UI for SingleProcessMash.
     devtools_server_ = ui_devtools::CreateUiDevToolsServerForViews(
         g_browser_process->system_network_context_manager()->GetContext());
   }
-#endif
 
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
   // On the Linux desktop, we want to prevent the user from logging in as root,
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h
index 9cb5fd4..c85cf3e 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h
@@ -15,10 +15,11 @@
 class ViewsDelegate;
 }
 
-#if defined(USE_AURA)
 namespace ui_devtools {
 class UiDevToolsServer;
 }
+
+#if defined(USE_AURA)
 namespace wm {
 class WMState;
 }
@@ -42,10 +43,9 @@
   std::unique_ptr<views::ViewsDelegate> views_delegate_;
   std::unique_ptr<views::LayoutProvider> layout_provider_;
 
-#if defined(USE_AURA)
   // Only used when running in --enable-ui-devtools.
   std::unique_ptr<ui_devtools::UiDevToolsServer> devtools_server_;
-
+#if defined(USE_AURA)
   std::unique_ptr<wm::WMState> wm_state_;
 #endif
 
diff --git a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
index 3643787..eb15ea1 100644
--- a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
@@ -172,10 +172,10 @@
 
   install_manager_->SetDataRetrieverFactoryForTesting(
       base::BindLambdaForTesting([&]() {
-        WebApplicationInfo info;
-        info.app_url = app_url;
-        auto data_retriever = std::make_unique<web_app::TestDataRetriever>(
-            std::make_unique<WebApplicationInfo>(std::move(info)));
+        auto info = std::make_unique<WebApplicationInfo>();
+        info->app_url = app_url;
+        auto data_retriever = std::make_unique<web_app::TestDataRetriever>();
+        data_retriever->SetRendererWebApplicationInfo(std::move(info));
 
         return std::unique_ptr<web_app::WebAppDataRetriever>(
             std::move(data_retriever));
@@ -264,10 +264,10 @@
 
   install_manager_->SetDataRetrieverFactoryForTesting(
       base::BindLambdaForTesting([&]() {
-        WebApplicationInfo info;
-        info.app_url = app_url;
-        auto data_retriever = std::make_unique<web_app::TestDataRetriever>(
-            std::make_unique<WebApplicationInfo>(std::move(info)));
+        auto info = std::make_unique<WebApplicationInfo>();
+        info->app_url = app_url;
+        auto data_retriever = std::make_unique<web_app::TestDataRetriever>();
+        data_retriever->SetRendererWebApplicationInfo(std::move(info));
 
         return std::unique_ptr<web_app::WebAppDataRetriever>(
             std::move(data_retriever));
diff --git a/chrome/browser/web_applications/components/install_manager.h b/chrome/browser/web_applications/components/install_manager.h
index dfdd639d..7d306e63 100644
--- a/chrome/browser/web_applications/components/install_manager.h
+++ b/chrome/browser/web_applications/components/install_manager.h
@@ -52,6 +52,7 @@
   // Starts a web app installation process for a given |web_contents|.
   // |force_shortcut_app| forces the creation of a shortcut app instead of a PWA
   // even if installation is available.
+  // TODO(loyso): Rename InstallWebApp to InstallWebAppFromManifestWithFallback.
   virtual void InstallWebApp(content::WebContents* web_contents,
                              bool force_shortcut_app,
                              WebappInstallSource install_source,
@@ -60,6 +61,7 @@
 
   // Starts a web app installation process for a given |web_contents|, initiated
   // by WebApp script. Bypasses the GetWebApplicationInfo from renderer step.
+  // TODO(loyso): Rename InstallWebAppFromBanner to InstallWebAppFromManifest.
   virtual void InstallWebAppFromBanner(
       content::WebContents* web_contents,
       WebappInstallSource install_source,
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
index 95039b5f..8df344e 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
@@ -165,11 +165,11 @@
 
     install_manager->SetDataRetrieverFactoryForTesting(
         base::BindLambdaForTesting([this]() {
-          WebApplicationInfo info;
-          info.app_url = app_url_;
-          info.title = base::UTF8ToUTF16(kWebAppTitle);
-          auto data_retriever = std::make_unique<web_app::TestDataRetriever>(
-              std::make_unique<WebApplicationInfo>(std::move(info)));
+          auto info = std::make_unique<WebApplicationInfo>();
+          info->app_url = app_url_;
+          info->title = base::UTF8ToUTF16(kWebAppTitle);
+          auto data_retriever = std::make_unique<web_app::TestDataRetriever>();
+          data_retriever->SetRendererWebApplicationInfo(std::move(info));
 
           return std::unique_ptr<web_app::WebAppDataRetriever>(
               std::move(data_retriever));
diff --git a/chrome/browser/web_applications/test/test_data_retriever.cc b/chrome/browser/web_applications/test/test_data_retriever.cc
index 4061a556..e9173eb 100644
--- a/chrome/browser/web_applications/test/test_data_retriever.cc
+++ b/chrome/browser/web_applications/test/test_data_retriever.cc
@@ -7,12 +7,11 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "chrome/common/web_application_info.h"
+#include "third_party/blink/public/common/manifest/manifest.h"
 
 namespace web_app {
 
-TestDataRetriever::TestDataRetriever(
-    std::unique_ptr<WebApplicationInfo> web_app_info)
-    : web_app_info_(std::move(web_app_info)) {}
+TestDataRetriever::TestDataRetriever() = default;
 
 TestDataRetriever::~TestDataRetriever() = default;
 
@@ -24,6 +23,20 @@
       FROM_HERE, base::BindOnce(std::move(callback), std::move(web_app_info_)));
 }
 
+void TestDataRetriever::CheckInstallabilityAndRetrieveManifest(
+    content::WebContents* web_contents,
+    CheckInstallabilityCallback callback) {
+  if (manifest_ == nullptr) {
+    WebAppDataRetriever::CheckInstallabilityAndRetrieveManifest(
+        web_contents, std::move(callback));
+    return;
+  }
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), *manifest_, is_installable_));
+}
+
 void TestDataRetriever::GetIcons(content::WebContents* web_contents,
                                  const std::vector<GURL>& icon_urls,
                                  bool skip_page_fav_icons,
@@ -33,6 +46,17 @@
   icons_map_.clear();
 }
 
+void TestDataRetriever::SetRendererWebApplicationInfo(
+    std::unique_ptr<WebApplicationInfo> web_app_info) {
+  web_app_info_ = std::move(web_app_info);
+}
+
+void TestDataRetriever::SetManifest(std::unique_ptr<blink::Manifest> manifest,
+                                    bool is_installable) {
+  manifest_ = std::move(manifest);
+  is_installable_ = is_installable;
+}
+
 void TestDataRetriever::SetIcons(IconsMap icons_map) {
   icons_map_ = std::move(icons_map);
 }
diff --git a/chrome/browser/web_applications/test/test_data_retriever.h b/chrome/browser/web_applications/test/test_data_retriever.h
index 2d6ef50b..098b929 100644
--- a/chrome/browser/web_applications/test/test_data_retriever.h
+++ b/chrome/browser/web_applications/test/test_data_retriever.h
@@ -19,17 +19,26 @@
 // when running callbacks to simulate async behavior in tests as well.
 class TestDataRetriever : public WebAppDataRetriever {
  public:
-  explicit TestDataRetriever(std::unique_ptr<WebApplicationInfo> web_app_info);
+  TestDataRetriever();
   ~TestDataRetriever() override;
 
   // WebAppDataRetriever:
   void GetWebApplicationInfo(content::WebContents* web_contents,
                              GetWebApplicationInfoCallback callback) override;
+  void CheckInstallabilityAndRetrieveManifest(
+      content::WebContents* web_contents,
+      CheckInstallabilityCallback callback) override;
   void GetIcons(content::WebContents* web_contents,
                 const std::vector<GURL>& icon_urls,
                 bool skip_page_fav_icons,
                 GetIconsCallback callback) override;
 
+  // Set info to respond on |GetWebApplicationInfo|.
+  void SetRendererWebApplicationInfo(
+      std::unique_ptr<WebApplicationInfo> web_app_info);
+  // Set arguments to respond on |CheckInstallabilityAndRetrieveManifest|.
+  void SetManifest(std::unique_ptr<blink::Manifest> manifest,
+                   bool is_installable);
   // Set icons to respond on |GetIcons|.
   void SetIcons(IconsMap icons_map);
 
@@ -37,6 +46,10 @@
 
  private:
   std::unique_ptr<WebApplicationInfo> web_app_info_;
+
+  std::unique_ptr<blink::Manifest> manifest_;
+  bool is_installable_;
+
   IconsMap icons_map_;
 
   DISALLOW_COPY_AND_ASSIGN(TestDataRetriever);
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index dd6d21f4..8ad1c3b 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -41,7 +41,7 @@
   DCHECK(AreWebAppsUserInstallable(profile_));
 
   auto task = std::make_unique<WebAppInstallTask>(profile_, install_finalizer_);
-  task->InstallWebApp(
+  task->InstallWebAppFromManifestWithFallback(
       contents, force_shortcut_app, install_source, std::move(dialog_callback),
       base::BindOnce(&WebAppInstallManager::OnTaskCompleted,
                      base::Unretained(this), task.get(), std::move(callback)));
@@ -54,8 +54,13 @@
     WebappInstallSource install_source,
     WebAppInstallDialogCallback dialog_callback,
     OnceInstallCallback callback) {
-  // TODO(loyso): Implement it.
-  NOTIMPLEMENTED();
+  auto task = std::make_unique<WebAppInstallTask>(profile_, install_finalizer_);
+  task->InstallWebAppFromManifest(
+      contents, install_source, std::move(dialog_callback),
+      base::BindOnce(&WebAppInstallManager::OnTaskCompleted,
+                     base::Unretained(this), task.get(), std::move(callback)));
+
+  tasks_.insert(std::move(task));
 }
 
 void WebAppInstallManager::InstallWebAppFromInfo(
diff --git a/chrome/browser/web_applications/web_app_install_task.cc b/chrome/browser/web_applications/web_app_install_task.cc
index 10d5553..d8df17b0 100644
--- a/chrome/browser/web_applications/web_app_install_task.cc
+++ b/chrome/browser/web_applications/web_app_install_task.cc
@@ -30,18 +30,34 @@
 
 WebAppInstallTask::~WebAppInstallTask() = default;
 
-void WebAppInstallTask::InstallWebApp(
+void WebAppInstallTask::InstallWebAppFromManifest(
+    content::WebContents* contents,
+    WebappInstallSource install_source,
+    InstallManager::WebAppInstallDialogCallback dialog_callback,
+    InstallManager::OnceInstallCallback install_callback) {
+  CheckInstallPreconditions();
+
+  Observe(contents);
+  dialog_callback_ = std::move(dialog_callback);
+  install_callback_ = std::move(install_callback);
+  install_source_ = install_source;
+
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+
+  data_retriever_->CheckInstallabilityAndRetrieveManifest(
+      web_contents(),
+      base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck,
+                     base::Unretained(this), std::move(web_app_info),
+                     /*force_shortcut_app=*/false));
+}
+
+void WebAppInstallTask::InstallWebAppFromManifestWithFallback(
     content::WebContents* contents,
     bool force_shortcut_app,
     WebappInstallSource install_source,
     InstallManager::WebAppInstallDialogCallback dialog_callback,
     InstallManager::OnceInstallCallback install_callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(AreWebAppsUserInstallable(profile_));
-
-  // Concurrent calls are not allowed.
-  DCHECK(!web_contents());
-  CHECK(!install_callback_);
+  CheckInstallPreconditions();
 
   Observe(contents);
   dialog_callback_ = std::move(dialog_callback);
@@ -51,7 +67,7 @@
   data_retriever_->GetWebApplicationInfo(
       web_contents(),
       base::BindOnce(&WebAppInstallTask::OnGetWebApplicationInfo,
-                     weak_ptr_factory_.GetWeakPtr(), force_shortcut_app));
+                     base::Unretained(this), force_shortcut_app));
 }
 
 void WebAppInstallTask::WebContentsDestroyed() {
@@ -68,6 +84,15 @@
   install_finalizer_ = install_finalizer;
 }
 
+void WebAppInstallTask::CheckInstallPreconditions() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(AreWebAppsUserInstallable(profile_));
+
+  // Concurrent calls are not allowed.
+  DCHECK(!web_contents());
+  CHECK(!install_callback_);
+}
+
 void WebAppInstallTask::CallInstallCallback(const AppId& app_id,
                                             InstallResultCode code) {
   Observe(nullptr);
@@ -103,7 +128,7 @@
   data_retriever_->CheckInstallabilityAndRetrieveManifest(
       web_contents(),
       base::BindOnce(&WebAppInstallTask::OnDidPerformInstallableCheck,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(web_app_info),
+                     base::Unretained(this), std::move(web_app_info),
                      force_shortcut_app));
 }
 
@@ -138,7 +163,7 @@
   data_retriever_->GetIcons(
       web_contents(), icon_urls, skip_page_fav_icons,
       base::BindOnce(&WebAppInstallTask::OnIconsRetrieved,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(web_app_info),
+                     base::Unretained(this), std::move(web_app_info),
                      for_installable_site));
 }
 
diff --git a/chrome/browser/web_applications/web_app_install_task.h b/chrome/browser/web_applications/web_app_install_task.h
index 8c727bd..02909514 100644
--- a/chrome/browser/web_applications/web_app_install_task.h
+++ b/chrome/browser/web_applications/web_app_install_task.h
@@ -36,7 +36,19 @@
   WebAppInstallTask(Profile* profile, InstallFinalizer* install_finalizer);
   ~WebAppInstallTask() override;
 
-  void InstallWebApp(
+  // Checks a WebApp installability, retrieves manifest and icons and
+  // than performs the actual installation.
+  void InstallWebAppFromManifest(
+      content::WebContents* web_contents,
+      WebappInstallSource install_source,
+      InstallManager::WebAppInstallDialogCallback dialog_callback,
+      InstallManager::OnceInstallCallback callback);
+
+  // This method infers WebApp info from the blink renderer process
+  // and than retrieves a manifest in a way similar to
+  // |InstallWebAppFromManifest|. If manifest is incomplete or missing, the
+  // inferred info is used.
+  void InstallWebAppFromManifestWithFallback(
       content::WebContents* web_contents,
       bool force_shortcut_app,
       WebappInstallSource install_source,
@@ -51,6 +63,8 @@
   void SetInstallFinalizerForTesting(InstallFinalizer* install_finalizer);
 
  private:
+  void CheckInstallPreconditions();
+
   // Calling the callback may destroy |this| task. Callers shoudln't work with
   // any |this| class members after calling it.
   void CallInstallCallback(const AppId& app_id, InstallResultCode code);
diff --git a/chrome/browser/web_applications/web_app_install_task_unittest.cc b/chrome/browser/web_applications/web_app_install_task_unittest.cc
index 0eb838d..2d1125c 100644
--- a/chrome/browser/web_applications/web_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_task_unittest.cc
@@ -119,11 +119,19 @@
         profile(), install_finalizer_.get());
   }
 
+  void CreateTestDataRetriever() {
+    auto data_retriever = std::make_unique<TestDataRetriever>();
+    data_retriever_ = data_retriever.get();
+    install_task_->SetDataRetrieverForTesting(std::move(data_retriever));
+  }
+
   void CreateRendererAppInfo(const GURL& url,
                              const std::string name,
                              const std::string description,
                              const GURL& scope,
                              base::Optional<SkColor> theme_color) {
+    CreateTestDataRetriever();
+
     auto web_app_info = std::make_unique<WebApplicationInfo>();
 
     web_app_info->app_url = url;
@@ -133,10 +141,7 @@
     web_app_info->theme_color = theme_color;
     web_app_info->open_as_window = true;
 
-    auto data_retriever =
-        std::make_unique<TestDataRetriever>(std::move(web_app_info));
-    data_retriever_ = data_retriever.get();
-    install_task_->SetDataRetrieverForTesting(std::move(data_retriever));
+    data_retriever_->SetRendererWebApplicationInfo(std::move(web_app_info));
   }
 
   void CreateRendererAppInfo(const GURL& url,
@@ -178,11 +183,11 @@
     InstallResultCode code;
   };
 
-  InstallResult InstallWebAppAndGetResults() {
+  InstallResult InstallWebAppFromManifestWithFallbackAndGetResults() {
     InstallResult result;
     base::RunLoop run_loop;
     const bool force_shortcut_app = false;
-    install_task_->InstallWebApp(
+    install_task_->InstallWebAppFromManifestWithFallback(
         web_contents(), force_shortcut_app,
         WebappInstallSource::MENU_BROWSER_TAB,
         base::BindOnce(TestAcceptDialogCallback),
@@ -196,8 +201,8 @@
     return result;
   }
 
-  AppId InstallWebApp() {
-    InstallResult result = InstallWebAppAndGetResults();
+  AppId InstallWebAppFromManifestWithFallback() {
+    InstallResult result = InstallWebAppFromManifestWithFallbackAndGetResults();
     DCHECK_EQ(InstallResultCode::kSuccess, result.code);
     return result.app_id;
   }
@@ -246,7 +251,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -281,7 +286,7 @@
   CreateRendererAppInfo(url, name, description);
   CreateDefaultInstallableManager();
 
-  const AppId installed_web_app = InstallWebApp();
+  const AppId installed_web_app = InstallWebAppFromManifestWithFallback();
   EXPECT_EQ(app_id, installed_web_app);
 
   // Second attempt.
@@ -291,7 +296,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -307,8 +312,9 @@
 }
 
 TEST_F(WebAppInstallTaskTest, GetWebApplicationInfoFailed) {
-  install_task_->SetDataRetrieverForTesting(std::make_unique<TestDataRetriever>(
-      std::unique_ptr<WebApplicationInfo>()));
+  auto data_retriver = std::make_unique<TestDataRetriever>();
+  // data_retriver with empty info means an error.
+  install_task_->SetDataRetrieverForTesting(std::move(data_retriver));
 
   CreateDefaultInstallableManager();
 
@@ -316,7 +322,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -340,7 +346,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -389,7 +395,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -430,7 +436,7 @@
       GenerateIconsMapWithOneIcon(icon_url, icon_size::k128, color);
   SetIconsMapToRetrieve(std::move(icons_map));
 
-  InstallWebApp();
+  InstallWebAppFromManifestWithFallback();
 
   std::unique_ptr<WebApplicationInfo> web_app_info =
       test_install_finalizer().web_app_info();
@@ -459,7 +465,7 @@
   IconsMap icons_map;
   SetIconsMapToRetrieve(std::move(icons_map));
 
-  InstallWebApp();
+  InstallWebAppFromManifestWithFallback();
 
   std::unique_ptr<WebApplicationInfo> web_app_info =
       test_install_finalizer().web_app_info();
@@ -495,7 +501,7 @@
     data_retriever_->web_app_info().icons.push_back(std::move(icon_info));
   }
 
-  const AppId app_id = InstallWebApp();
+  const AppId app_id = InstallWebAppFromManifestWithFallback();
 
   EXPECT_TRUE(file_utils_->DirectoryExists(web_apps_dir));
 
@@ -564,7 +570,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestAcceptDialogCallback),
       base::BindLambdaForTesting(
@@ -597,7 +603,7 @@
   bool callback_called = false;
   const bool force_shortcut_app = false;
 
-  install_task_->InstallWebApp(
+  install_task_->InstallWebAppFromManifestWithFallback(
       web_contents(), force_shortcut_app, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindOnce(TestDeclineDialogCallback),
       base::BindLambdaForTesting(
@@ -618,7 +624,7 @@
 TEST_F(WebAppInstallTaskTest, FinalizerMethodsCalled) {
   PrepareTestAppInstall();
 
-  InstallWebApp();
+  InstallWebAppFromManifestWithFallback();
 
   EXPECT_EQ(1, test_install_finalizer().num_create_os_shortcuts_calls());
   EXPECT_EQ(1, test_install_finalizer().num_reparent_tab_calls());
@@ -631,7 +637,7 @@
   test_install_finalizer().SetNextFinalizeInstallResult(
       AppId(), InstallResultCode::kFailedUnknownReason);
 
-  InstallResult result = InstallWebAppAndGetResults();
+  InstallResult result = InstallWebAppFromManifestWithFallbackAndGetResults();
 
   EXPECT_TRUE(result.app_id.empty());
   EXPECT_EQ(InstallResultCode::kFailedUnknownReason, result.code);
@@ -642,6 +648,32 @@
   EXPECT_EQ(0, test_install_finalizer().num_pin_app_to_shelf_calls());
 }
 
+TEST_F(WebAppInstallTaskTest, InstallWebAppFromManifest_Success) {
+  CreateTestDataRetriever();
+
+  const GURL url = GURL("https://example.com/path");
+  const AppId app_id = GenerateAppIdFromURL(url);
+
+  auto manifest = std::make_unique<blink::Manifest>();
+  manifest->start_url = url;
+
+  data_retriever_->SetManifest(std::move(manifest), /*is_installable=*/true);
+
+  base::RunLoop run_loop;
+
+  install_task_->InstallWebAppFromManifest(
+      web_contents(), WebappInstallSource::MENU_BROWSER_TAB,
+      base::BindOnce(TestAcceptDialogCallback),
+      base::BindLambdaForTesting(
+          [&](const AppId& installed_app_id, InstallResultCode code) {
+            EXPECT_EQ(InstallResultCode::kSuccess, code);
+            EXPECT_EQ(app_id, installed_app_id);
+            run_loop.Quit();
+          }));
+
+  run_loop.Run();
+}
+
 // TODO(loyso): Convert more tests from bookmark_app_helper_unittest.cc
 
 }  // namespace web_app
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index aab754f..bf89528 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -410,13 +410,13 @@
 #endif
 
   for (auto& origin_or_hostname_pattern : network::GetSecureOriginAllowlist()) {
-    WebSecurityPolicy::AddOriginTrustworthyWhiteList(
+    WebSecurityPolicy::AddOriginToTrustworthySafelist(
         WebString::FromUTF8(origin_or_hostname_pattern));
   }
 
   for (auto& scheme :
        secure_origin_whitelist::GetSchemesBypassingSecureContextCheck()) {
-    WebSecurityPolicy::AddSchemeToBypassSecureContextWhitelist(
+    WebSecurityPolicy::AddSchemeToSecureContextSafelist(
         WebString::FromASCII(scheme));
   }
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index a6239ba..ac5701c 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -178,7 +178,7 @@
     "//components/webdata/common:unit_tests",
   ]
 
-  if (toolkit_views && use_aura) {
+  if (toolkit_views) {
     deps += [
       "//components/ui_devtools:unit_tests",
       "//components/ui_devtools/views:unit_tests",
diff --git a/components/autofill_assistant/browser/actions/action_delegate.h b/components/autofill_assistant/browser/actions/action_delegate.h
index d894a7d..69daf7f 100644
--- a/components/autofill_assistant/browser/actions/action_delegate.h
+++ b/components/autofill_assistant/browser/actions/action_delegate.h
@@ -63,14 +63,26 @@
   virtual void ShortWaitForElement(const Selector& selector,
                                    base::OnceCallback<void(bool)> callback) = 0;
 
+  enum class SelectorPredicate {
+    // The selector matches elements
+    kMatches,
+
+    // The selector doesn't match any elements
+    kDoesntMatch
+  };
+
   // Wait for up to |max_wait_time| for the element |selectors| to match
   // element(s) on the page, then call |callback| with true if at least an
   // element matched, false otherwise.
   //
+  // |selector_predicate| specifies the condition that must be satisfied for
+  // WaitForDom to return successfully. It applies to the given |selector|.
+  //
   // If |allow_interrupt| interrupts can run while waiting.
-  virtual void WaitForElement(
+  virtual void WaitForDom(
       base::TimeDelta max_wait_time,
       bool allow_interrupt,
+      SelectorPredicate selector_predicate,
       const Selector& selector,
       base::OnceCallback<void(ProcessedActionStatusProto)> callback) = 0;
 
diff --git a/components/autofill_assistant/browser/actions/mock_action_delegate.h b/components/autofill_assistant/browser/actions/mock_action_delegate.h
index e1b3816..4beef38 100644
--- a/components/autofill_assistant/browser/actions/mock_action_delegate.h
+++ b/components/autofill_assistant/browser/actions/mock_action_delegate.h
@@ -33,17 +33,20 @@
   MOCK_METHOD2(OnShortWaitForElement,
                void(const Selector& selector, base::OnceCallback<void(bool)>&));
 
-  void WaitForElement(
+  void WaitForDom(
       base::TimeDelta max_wait_time,
       bool allow_interrupt,
+      ActionDelegate::SelectorPredicate selector_predicate,
       const Selector& selector,
       base::OnceCallback<void(ProcessedActionStatusProto)> callback) override {
-    OnWaitForElement(max_wait_time, allow_interrupt, selector, callback);
+    OnWaitForDom(max_wait_time, allow_interrupt, selector_predicate, selector,
+                 callback);
   }
 
-  MOCK_METHOD4(OnWaitForElement,
+  MOCK_METHOD5(OnWaitForDom,
                void(base::TimeDelta,
                     bool,
+                    ActionDelegate::SelectorPredicate,
                     const Selector&,
                     base::OnceCallback<void(ProcessedActionStatusProto)>&));
 
diff --git a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
index a58a7a7..c0127e86 100644
--- a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
+++ b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
@@ -27,20 +27,32 @@
 
 void WaitForDomAction::InternalProcessAction(ActionDelegate* delegate,
                                              ProcessActionCallback callback) {
-  Selector selector = Selector(proto_.wait_for_dom().wait_until());
-  if (selector.empty()) {
-    DVLOG(1) << __func__ << ": no selector specified for WaitForDom";
-    OnCheckDone(std::move(callback), INVALID_SELECTOR);
-    return;
-  }
-
   base::TimeDelta max_wait_time = kDefaultCheckDuration;
   int timeout_ms = proto_.wait_for_dom().timeout_ms();
   if (timeout_ms > 0)
     max_wait_time = base::TimeDelta::FromMilliseconds(timeout_ms);
 
-  delegate->WaitForElement(
-      max_wait_time, proto_.wait_for_dom().allow_interrupt(), selector,
+  Selector wait_until = Selector(proto_.wait_for_dom().wait_until());
+  Selector wait_while = Selector(proto_.wait_for_dom().wait_while());
+  ActionDelegate::SelectorPredicate selector_predicate;
+  Selector selector;
+  if (!wait_until.empty()) {
+    // wait until the selector matches something
+    selector_predicate = ActionDelegate::SelectorPredicate::kMatches;
+    selector = wait_until;
+  } else if (!wait_while.empty()) {
+    // wait as long as the selector matches something
+    selector_predicate = ActionDelegate::SelectorPredicate::kDoesntMatch;
+    selector = wait_while;
+  } else {
+    DVLOG(1) << __func__ << ": no selector specified for WaitForDom";
+    OnCheckDone(std::move(callback), INVALID_SELECTOR);
+    return;
+  }
+
+  delegate->WaitForDom(
+      max_wait_time, proto_.wait_for_dom().allow_interrupt(),
+      selector_predicate, selector,
       base::BindOnce(&WaitForDomAction::OnCheckDone,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
diff --git a/components/autofill_assistant/browser/script_executor.cc b/components/autofill_assistant/browser/script_executor.cc
index a12eb344..fb81192d 100644
--- a/components/autofill_assistant/browser/script_executor.cc
+++ b/components/autofill_assistant/browser/script_executor.cc
@@ -126,27 +126,31 @@
 void ScriptExecutor::ShortWaitForElement(
     const Selector& selector,
     base::OnceCallback<void(bool)> callback) {
-  WaitForElement(kShortWaitForElementDeadline, selector, std::move(callback));
+  retry_timer_.Start(
+      kShortWaitForElementDeadline,
+      base::BindRepeating(&ScriptExecutor::CheckForElement,
+                          weak_ptr_factory_.GetWeakPtr(), selector),
+      std::move(callback));
 }
 
-void ScriptExecutor::WaitForElement(
+void ScriptExecutor::CheckForElement(const Selector& selector,
+                                     base::OnceCallback<void(bool)> callback) {
+  delegate_->GetWebController()->ElementCheck(selector,
+                                              /* strict= */ false,
+                                              std::move(callback));
+}
+
+void ScriptExecutor::WaitForDom(
     base::TimeDelta max_wait_time,
     bool allow_interrupt,
+    ActionDelegate::SelectorPredicate selector_predicate,
     const Selector& selector,
     base::OnceCallback<void(ProcessedActionStatusProto)> callback) {
-  if (!allow_interrupt || ordered_interrupts_->empty()) {
-    // No interrupts to worry about. Just run normal wait.
-    WaitForElement(
-        max_wait_time, selector,
-        base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleNoInterrupts,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-    return;
-  }
-  wait_with_interrupts_ = std::make_unique<WaitWithInterrupts>(
-      this, max_wait_time, selector,
+  wait_for_dom_ = std::make_unique<WaitForDomOperation>(
+      this, max_wait_time, allow_interrupt, selector_predicate, selector,
       base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleWithInterrupts,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  wait_with_interrupts_->Run();
+  wait_for_dom_->Run();
 }
 
 void ScriptExecutor::SetStatusMessage(const std::string& message) {
@@ -359,8 +363,8 @@
 }
 
 void ScriptExecutor::Terminate() {
-  if (wait_with_interrupts_)
-    wait_with_interrupts_->Terminate();
+  if (wait_for_dom_)
+    wait_for_dom_->Terminate();
   at_end_ = TERMINATE;
   should_stop_script_ = true;
 
@@ -550,23 +554,6 @@
   ProcessNextAction();
 }
 
-void ScriptExecutor::WaitForElement(base::TimeDelta max_wait_time,
-                                    const Selector& selector,
-                                    base::OnceCallback<void(bool)> callback) {
-  retry_timer_.Start(
-      max_wait_time,
-      base::BindRepeating(&ScriptExecutor::CheckForElement,
-                          weak_ptr_factory_.GetWeakPtr(), selector),
-      std::move(callback));
-}
-
-void ScriptExecutor::CheckForElement(const Selector& selector,
-                                     base::OnceCallback<void(bool)> callback) {
-  delegate_->GetWebController()->ElementCheck(selector,
-                                              /* strict= */ false,
-                                              std::move(callback));
-}
-
 void ScriptExecutor::OnWaitForElementVisibleWithInterrupts(
     base::OnceCallback<void(ProcessedActionStatusProto)> callback,
     bool element_found,
@@ -595,31 +582,35 @@
                                         : ELEMENT_RESOLUTION_FAILED);
 }
 
-ScriptExecutor::WaitWithInterrupts::WaitWithInterrupts(
+ScriptExecutor::WaitForDomOperation::WaitForDomOperation(
     ScriptExecutor* main_script,
     base::TimeDelta max_wait_time,
+    bool allow_interrupt,
+    ActionDelegate::SelectorPredicate selector_predicate,
     const Selector& selector,
-    WaitWithInterrupts::Callback callback)
+    WaitForDomOperation::Callback callback)
     : main_script_(main_script),
       max_wait_time_(max_wait_time),
+      allow_interrupt_(allow_interrupt),
+      selector_predicate_(selector_predicate),
       selector_(selector),
       callback_(std::move(callback)),
       retry_timer_(kPeriodicElementCheck),
       weak_ptr_factory_(this) {}
 
-ScriptExecutor::WaitWithInterrupts::~WaitWithInterrupts() = default;
+ScriptExecutor::WaitForDomOperation::~WaitForDomOperation() = default;
 
-void ScriptExecutor::WaitWithInterrupts::Run() {
+void ScriptExecutor::WaitForDomOperation::Run() {
   retry_timer_.Start(
       max_wait_time_,
-      base::BindRepeating(&ScriptExecutor::WaitWithInterrupts::RunChecks,
+      base::BindRepeating(&ScriptExecutor::WaitForDomOperation::RunChecks,
                           // safe since this instance owns retry_timer_
                           base::Unretained(this)),
-      base::BindOnce(&ScriptExecutor::WaitWithInterrupts::RunCallback,
+      base::BindOnce(&ScriptExecutor::WaitForDomOperation::RunCallback,
                      base::Unretained(this)));
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnServerPayloadChanged(
+void ScriptExecutor::WaitForDomOperation::OnServerPayloadChanged(
     const std::string& global_payload,
     const std::string& script_payload) {
   // Interrupts and main scripts share global payloads, but not script payloads.
@@ -627,57 +618,73 @@
   main_script_->ReportPayloadsToListener();
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnScriptListChanged(
+void ScriptExecutor::WaitForDomOperation::OnScriptListChanged(
     std::vector<std::unique_ptr<Script>> scripts) {
   main_script_->ReportScriptsUpdateToListener(std::move(scripts));
 }
 
-void ScriptExecutor::WaitWithInterrupts::RunChecks(
+void ScriptExecutor::WaitForDomOperation::RunChecks(
     base::OnceCallback<void(bool)> report_attempt_result) {
   // Reset state possibly left over from previous runs.
-  element_found_ = false;
+  element_check_result_ = false;
   runnable_interrupts_.clear();
   batch_element_checker_ = std::make_unique<BatchElementChecker>();
   batch_element_checker_->AddElementCheck(
-      selector_, base::BindOnce(&WaitWithInterrupts::OnElementCheckDone,
+      selector_, base::BindOnce(&WaitForDomOperation::OnElementCheckDone,
                                 base::Unretained(this)));
-  for (const auto* interrupt : *main_script_->ordered_interrupts_) {
-    if (ran_interrupts_.find(interrupt->handle.path) != ran_interrupts_.end()) {
-      // Only run an interrupt once in a WaitWithInterrupts, to avoid loops.
-      continue;
-    }
+  if (allow_interrupt_) {
+    for (const auto* interrupt : *main_script_->ordered_interrupts_) {
+      if (ran_interrupts_.find(interrupt->handle.path) !=
+          ran_interrupts_.end()) {
+        // Only run an interrupt once in a WaitForDom, to avoid loops.
+        continue;
+      }
 
-    interrupt->precondition->Check(
-        main_script_->delegate_->GetCurrentURL(), batch_element_checker_.get(),
-        main_script_->delegate_->GetTriggerContext()->script_parameters,
-        *main_script_->scripts_state_,
-        base::BindOnce(&WaitWithInterrupts::OnPreconditionCheckDone,
-                       weak_ptr_factory_.GetWeakPtr(),
-                       base::Unretained(interrupt)));
+      interrupt->precondition->Check(
+          main_script_->delegate_->GetCurrentURL(),
+          batch_element_checker_.get(),
+          main_script_->delegate_->GetTriggerContext()->script_parameters,
+          *main_script_->scripts_state_,
+          base::BindOnce(&WaitForDomOperation::OnPreconditionCheckDone,
+                         weak_ptr_factory_.GetWeakPtr(),
+                         base::Unretained(interrupt)));
+    }
+    // The base::Unretained(this) above are safe, since the pointers belong to
+    // the main script, which own this instance.
   }
-  // The base::Unretained(this) above are safe, since the pointers belong to the
-  // main script, which own this instance.
 
   batch_element_checker_->Run(
       main_script_->delegate_->GetWebController(),
-      base::BindOnce(&WaitWithInterrupts::OnAllChecksDone,
+      base::BindOnce(&WaitForDomOperation::OnAllChecksDone,
                      base::Unretained(this), std::move(report_attempt_result)));
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnPreconditionCheckDone(
+void ScriptExecutor::WaitForDomOperation::OnPreconditionCheckDone(
     const Script* interrupt,
     bool precondition_match) {
   if (precondition_match)
     runnable_interrupts_.insert(interrupt);
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnElementCheckDone(bool found) {
-  element_found_ = found;
+void ScriptExecutor::WaitForDomOperation::OnElementCheckDone(bool found) {
+  switch (selector_predicate_) {
+    case ActionDelegate::SelectorPredicate::kMatches:
+      element_check_result_ = found;
+      break;
+
+    case ActionDelegate::SelectorPredicate::kDoesntMatch:
+      element_check_result_ = !found;
+      break;
+
+      // Default intentionally left unset to cause a compilation error if a new
+      // value is added.
+  }
+
   // Wait for all checks to run before reporting that the element was found to
   // the caller, so interrupts have a chance to run.
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnAllChecksDone(
+void ScriptExecutor::WaitForDomOperation::OnAllChecksDone(
     base::OnceCallback<void(bool)> report_attempt_result) {
   if (!runnable_interrupts_.empty()) {
     // We must go through runnable_interrupts_ to make sure priority order is
@@ -689,10 +696,11 @@
       }
     }
   }
-  std::move(report_attempt_result).Run(element_found_);
+  std::move(report_attempt_result).Run(element_check_result_);
 }
 
-void ScriptExecutor::WaitWithInterrupts::RunInterrupt(const Script* interrupt) {
+void ScriptExecutor::WaitForDomOperation::RunInterrupt(
+    const Script* interrupt) {
   batch_element_checker_.reset();
   SavePreInterruptState();
   ran_interrupts_.insert(interrupt->handle.path);
@@ -702,12 +710,12 @@
       /* listener= */ this, main_script_->scripts_state_, &no_interrupts_,
       main_script_->delegate_);
   interrupt_executor_->Run(
-      base::BindOnce(&ScriptExecutor::WaitWithInterrupts::OnInterruptDone,
+      base::BindOnce(&ScriptExecutor::WaitForDomOperation::OnInterruptDone,
                      base::Unretained(this)));
   // base::Unretained(this) is safe because interrupt_executor_ belongs to this
 }
 
-void ScriptExecutor::WaitWithInterrupts::OnInterruptDone(
+void ScriptExecutor::WaitForDomOperation::OnInterruptDone(
     const ScriptExecutor::Result& result) {
   interrupt_executor_.reset();
   if (!result.success || result.at_end != ScriptExecutor::CONTINUE) {
@@ -718,16 +726,16 @@
 
   // Restart. We use the original wait time since the interruption could have
   // triggered any kind of actions, including actions that wait on the user. We
-  // don't trust a previous element_found_ result, since it could have changed.
+  // don't trust a previous element_check_result_, since it could have changed.
   Run();
 }
 
-void ScriptExecutor::WaitWithInterrupts::RunCallback(bool found) {
+void ScriptExecutor::WaitForDomOperation::RunCallback(bool found) {
   RunCallbackWithResult(found, nullptr);
 }
 
-void ScriptExecutor::WaitWithInterrupts::RunCallbackWithResult(
-    bool found,
+void ScriptExecutor::WaitForDomOperation::RunCallbackWithResult(
+    bool check_result,
     const ScriptExecutor::Result* result) {
   // stop element checking if one is still in progress
   batch_element_checker_.reset();
@@ -735,11 +743,11 @@
   if (!callback_)
     return;
 
-  RestorePreInterruptScroll(found);
-  std::move(callback_).Run(found, result, ran_interrupts_);
+  RestorePreInterruptScroll(check_result);
+  std::move(callback_).Run(check_result, result, ran_interrupts_);
 }
 
-void ScriptExecutor::WaitWithInterrupts::SavePreInterruptState() {
+void ScriptExecutor::WaitForDomOperation::SavePreInterruptState() {
   if (saved_pre_interrupt_state_)
     return;
 
@@ -747,20 +755,21 @@
   saved_pre_interrupt_state_ = true;
 }
 
-void ScriptExecutor::WaitWithInterrupts::RestoreStatusMessage() {
+void ScriptExecutor::WaitForDomOperation::RestoreStatusMessage() {
   if (!saved_pre_interrupt_state_)
     return;
 
   main_script_->delegate_->SetStatusMessage(pre_interrupt_status_);
 }
 
-void ScriptExecutor::WaitWithInterrupts::RestorePreInterruptScroll(
-    bool element_found) {
+void ScriptExecutor::WaitForDomOperation::RestorePreInterruptScroll(
+    bool check_result) {
   if (!saved_pre_interrupt_state_)
     return;
 
   auto* delegate = main_script_->delegate_;
-  if (element_found) {
+  if (check_result &&
+      selector_predicate_ == ActionDelegate::SelectorPredicate::kMatches) {
     delegate->GetWebController()->FocusElement(selector_, base::DoNothing());
   } else if (!main_script_->last_focused_element_selector_.empty()) {
     delegate->GetWebController()->FocusElement(
@@ -768,7 +777,7 @@
   }
 }
 
-void ScriptExecutor::WaitWithInterrupts::Terminate() {
+void ScriptExecutor::WaitForDomOperation::Terminate() {
   if (interrupt_executor_)
     interrupt_executor_->Terminate();
 }
diff --git a/components/autofill_assistant/browser/script_executor.h b/components/autofill_assistant/browser/script_executor.h
index 3e0081f7..ddf1045a4 100644
--- a/components/autofill_assistant/browser/script_executor.h
+++ b/components/autofill_assistant/browser/script_executor.h
@@ -106,9 +106,10 @@
                         base::OnceCallback<void()> all_done) override;
   void ShortWaitForElement(const Selector& selector,
                            base::OnceCallback<void(bool)> callback) override;
-  void WaitForElement(
+  void WaitForDom(
       base::TimeDelta max_wait_time,
       bool allow_interrupt,
+      ActionDelegate::SelectorPredicate selector_predicate,
       const Selector& selector,
       base::OnceCallback<void(ProcessedActionStatusProto)> callback) override;
   void SetStatusMessage(const std::string& message) override;
@@ -180,7 +181,7 @@
  private:
   // Helper for WaitForElementVisible that keeps track of the state required to
   // run interrupts while waiting for a specific element.
-  class WaitWithInterrupts : public ScriptExecutor::Listener {
+  class WaitForDomOperation : public ScriptExecutor::Listener {
    public:
     // Let the caller know about either the result of looking for the element or
     // of an abnormal result from an interrupt.
@@ -195,11 +196,13 @@
                                              const std::set<std::string>&)>;
 
     // |main_script_| must not be null and outlive this instance.
-    WaitWithInterrupts(ScriptExecutor* main_script,
-                       base::TimeDelta max_wait_time,
-                       const Selector& selectors,
-                       WaitWithInterrupts::Callback callback);
-    ~WaitWithInterrupts() override;
+    WaitForDomOperation(ScriptExecutor* main_script,
+                        base::TimeDelta max_wait_time,
+                        bool allow_interrupt,
+                        ActionDelegate::SelectorPredicate selector_predicate,
+                        const Selector& selectors,
+                        WaitForDomOperation::Callback callback);
+    ~WaitForDomOperation() override;
 
     void Run();
     void Terminate();
@@ -234,12 +237,14 @@
 
     ScriptExecutor* main_script_;
     const base::TimeDelta max_wait_time_;
+    const bool allow_interrupt_;
+    const ActionDelegate::SelectorPredicate selector_predicate_;
     const Selector selector_;
-    WaitWithInterrupts::Callback callback_;
+    WaitForDomOperation::Callback callback_;
 
     std::unique_ptr<BatchElementChecker> batch_element_checker_;
     std::set<const Script*> runnable_interrupts_;
-    bool element_found_ = false;
+    bool element_check_result_ = false;
 
     // An empty vector of interrupts that can be passed to interrupt_executor_
     // and outlives it. Interrupts must not run interrupts.
@@ -260,11 +265,10 @@
 
     RetryTimer retry_timer_;
 
-    base::WeakPtrFactory<WaitWithInterrupts> weak_ptr_factory_;
+    base::WeakPtrFactory<WaitForDomOperation> weak_ptr_factory_;
 
-    DISALLOW_COPY_AND_ASSIGN(WaitWithInterrupts);
+    DISALLOW_COPY_AND_ASSIGN(WaitForDomOperation);
   };
-  friend class WaitWithInterrupts;
 
   void OnGetActions(bool result, const std::string& response);
   bool ProcessNextActionResponse(const std::string& response);
@@ -326,7 +330,7 @@
   // vector first should run first.
   const std::vector<Script*>* ordered_interrupts_;
 
-  std::unique_ptr<WaitWithInterrupts> wait_with_interrupts_;
+  std::unique_ptr<WaitForDomOperation> wait_for_dom_;
 
   // Callback set by Prompt(). This is called when the prompt is terminated
   // without selecting any chips. nullptr unless showing a prompt.
diff --git a/components/autofill_assistant/browser/script_executor_unittest.cc b/components/autofill_assistant/browser/script_executor_unittest.cc
index 944a513..bebb92c 100644
--- a/components/autofill_assistant/browser/script_executor_unittest.cc
+++ b/components/autofill_assistant/browser/script_executor_unittest.cc
@@ -495,6 +495,64 @@
   EXPECT_EQ("actions payload", last_script_payload_);
 }
 
+TEST_F(ScriptExecutorTest, WaitForDomWaitUntil) {
+  ActionsResponseProto actions_response;
+  auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
+  wait_for_dom->mutable_wait_until()->add_selectors("element");
+
+  EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
+      .WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
+  std::vector<ProcessedActionProto> processed_actions_capture;
+  EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
+                      RunOnceCallback<4>(true, "")));
+
+  // First check does not find the element, wait for dom waits 1s, then the
+  // element is found, and the action succeeds.
+  EXPECT_CALL(mock_web_controller_,
+              OnElementCheck(Eq(Selector({"element"})), _))
+      .WillOnce(RunOnceCallback<1>(false));
+  executor_->Run(executor_callback_.Get());
+
+  EXPECT_CALL(mock_web_controller_,
+              OnElementCheck(Eq(Selector({"element"})), _))
+      .WillRepeatedly(RunOnceCallback<1>(true));
+  EXPECT_CALL(executor_callback_, Run(_));
+  scoped_task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+
+  ASSERT_EQ(1u, processed_actions_capture.size());
+  EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
+}
+
+TEST_F(ScriptExecutorTest, WaitForDomWaitWhile) {
+  ActionsResponseProto actions_response;
+  auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
+  wait_for_dom->mutable_wait_while()->add_selectors("element");
+
+  EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
+      .WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
+  std::vector<ProcessedActionProto> processed_actions_capture;
+  EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
+                      RunOnceCallback<4>(true, "")));
+
+  // First check finds the element, wait for dom waits 1s, then the element
+  // disappears, and the action succeeds.
+  EXPECT_CALL(mock_web_controller_,
+              OnElementCheck(Eq(Selector({"element"})), _))
+      .WillOnce(RunOnceCallback<1>(true));
+  executor_->Run(executor_callback_.Get());
+
+  EXPECT_CALL(mock_web_controller_,
+              OnElementCheck(Eq(Selector({"element"})), _))
+      .WillRepeatedly(RunOnceCallback<1>(false));
+  EXPECT_CALL(executor_callback_, Run(_));
+  scoped_task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+
+  ASSERT_EQ(1u, processed_actions_capture.size());
+  EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
+}
+
 TEST_F(ScriptExecutorTest, RunInterrupt) {
   // All elements exist, so first the interrupt should be run, then the element
   // should be reported as found.
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index 27529a9..b5da414c 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -660,9 +660,15 @@
   // Fail after waiting this amount of time.
   optional int32 timeout_ms = 1;
 
-  // Wait until there exists at least one element that matches the given
-  // selector.
-  optional ElementReferenceProto wait_until = 5;
+  oneof wait_on {
+    // Wait until there exists at least one element that matches the given
+    // selector.
+    ElementReferenceProto wait_until = 5;
+
+    // Wait as long as there's at least one element that matches the given
+    // reference.
+    ElementReferenceProto wait_while = 6;
+  }
 
   // If true, run scripts flagged with 'interrupt=true' as soon as their
   // preconditions match.
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index c9d29c2d..cb3ea15 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -163,6 +163,23 @@
 
   // Previous DMToken that should be reused for re-registration.
   optional string reregistration_dm_token = 13;
+
+  // MAC address for onboard network (ethernet) interface.
+  // The format is twelve (12) hexadecimal digits without any delimiter
+  // (uppercase letters).
+  // This field might be set only if register type == DEVICE.
+  optional string ethernet_mac_address = 14;
+
+  // Built-in MAC address for the docking station that the device can be
+  // connected to.
+  // The format is twelve (12) hexadecimal digits without any delimiter
+  // (uppercase letters).
+  // This field might be set only if register type == DEVICE.
+  optional string dock_mac_address = 15;
+
+  // The date the device was manufactured in yyyy-mm-dd format.
+  // This field might be set only if register type == DEVICE.
+  optional string manufacture_date = 16;
 }
 
 // Response from server to device register request.
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index fe9e341..7b86e82 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -299,30 +299,27 @@
   ],
   'policy_definitions': [
     {
-      'name': 'Homepage',
+      'name': 'Startup',
       'type': 'group',
-      'caption': '''Home page''',
-      'desc': '''Configure the default home page in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing it.
+      'caption': '''Startup, Home page and New Tab page''',
+      'desc': '''Configure the pages to load on startup, the default home page and the default new tab page in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> and prevents users from changing them.
 
-      The user's home page settings are only completely locked down, if you either select the home page to be the new tab page, or set it to be a URL and specify a home page URL. If you don't specify the home page URL, then the user is still able to set the home page to the new tab page by specifying 'chrome://newtab'.''',
+      The user's home page settings are only completely locked down if you either select the home page to be the new tab page, or set it to be a URL and specify a home page URL. If you don't specify the home page URL, then the user is still able to set the home page to the new tab page by specifying 'chrome://newtab'.
+
+      The policy 'URLs to open on startup' is ignored unless you select 'Open a list of URLs' in 'Action on startup'.''',
       'policies': [
+        'ShowHomeButton',
         'HomepageLocation',
         'HomepageIsNewTabPage',
-      ],
-    },
-    {
-      'name': 'NewTabPage',
-      'type': 'group',
-      'caption': '''New Tab Page''',
-      'desc': '''Configure the default New Tab page in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>.''',
-      'policies': [
         'NewTabPageLocation',
+        'RestoreOnStartup',
+        'RestoreOnStartupURLs',
       ],
     },
     {
       'name': 'RemoteAccess',
       'type': 'group',
-      'caption': '''Configure remote access options''',
+      'caption': '''Remote access''',
       'desc': '''Configure remote access options in Chrome Remote Desktop host.
 
       Chrome Remote Desktop host is a native service that runs on the target machine that a user can connect to using Chrome Remote Desktop application.  The native service is packaged and executed separately from the <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> browser.
@@ -389,7 +386,7 @@
     {
       'name': 'HTTPAuthentication',
       'type': 'group',
-      'caption': '''Policies for HTTP authentication''',
+      'caption': '''HTTP authentication''',
       'desc': '''Policies related to integrated HTTP authentication.''',
       'policies': [
         'AuthSchemes',
@@ -422,18 +419,6 @@
       ],
     },
     {
-      'name': 'RestoreOnStartupGroup',
-      'type': 'group',
-      'caption': '''Startup pages''',
-      'desc': '''Allows you to configure the pages that are loaded on startup.
-
-      The policy 'URLs to open on startup' is ignored unless you select 'Open a list of URLs' in 'Action on startup'.''',
-      'policies': [
-        'RestoreOnStartup',
-        'RestoreOnStartupURLs',
-      ],
-    },
-    {
       # TODO(joaodasilva): Flag these policies with 'can_be_recommended'
       # after fixing https://crbug.com/106683
       'name': 'DefaultSearchProvider',
@@ -537,7 +522,7 @@
     {
       'name': 'Drive',
       'type': 'group',
-      'caption': '''Configure Google Drive options''',
+      'caption': '''Google Drive''',
       'desc': '''Configure Google Drive in <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph>.''',
       'policies': [
         'DriveDisabled',
@@ -649,7 +634,7 @@
     {
       'name': 'QuickUnlock',
       'type': 'group',
-      'caption': '''Quick unlock policies''',
+      'caption': '''Quick unlock''',
       'desc': '''Configures quick unlock related policies.''',
       'policies': [
         'QuickUnlockModeWhitelist',
@@ -3443,7 +3428,7 @@
       },
       'example_value': True,
       'id': 393,
-      'caption': '''Whether NTLMv2 authentication is enabled.''',
+      'caption': '''Enable NTLMv2 authentication.''',
       'tags': ['website-sharing'],
       'desc': '''Controls whether NTLMv2 is enabled.
 
@@ -6530,7 +6515,7 @@
       },
       'example_value': False,
       'id': 129,
-      'caption': '''Whether online OCSP/CRL checks are performed''',
+      'caption': '''Enable online OCSP/CRL checks''',
       'tags': ['website-sharing'],
       'desc': '''In light of the fact that soft-fail, online revocation checks provide no effective security benefit, they are disabled by default in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> version 19 and later. By setting this policy to true, the previous behavior is restored and online OCSP/CRL checks will be performed.
 
@@ -6547,7 +6532,7 @@
       },
       'example_value': False,
       'id': 235,
-      'caption': '''Whether online OCSP/CRL checks are required for local trust anchors''',
+      'caption': '''Require online OCSP/CRL checks for local trust anchors''',
       'tags': [],
       'desc': '''When this setting is enabled, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will always perform revocation checking for server certificates that successfully validate and are signed by locally-installed CA certificates.
 
@@ -6566,7 +6551,7 @@
       },
       'example_value': False,
       'id': 340,
-      'caption': '''Whether SHA-1 signed certificates issued by local trust anchors are allowed''',
+      'caption': '''Allow SHA-1 signed certificates issued by local trust anchors''',
       'tags': ['system-security'],
       'desc': '''When this setting is enabled, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> allows SHA-1 signed certificates as long as they successfully validate and chain to a locally-installed CA certificates.
 
@@ -6586,7 +6571,7 @@
       'deprecated': True,
       'example_value': False,
       'id': 366,
-      'caption': '''Whether to allow certificates issued by local trust anchors that are missing the subjectAlternativeName extension''',
+      'caption': '''Allow certificates issued by local trust anchors without subjectAlternativeName extension''',
       'tags': ['system-security'],
       'desc': '''When this setting is enabled, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will use the commonName of a server certificate to match a hostname if the certificate is missing a subjectAlternativeName extension, as long as it successfully validates and chains to a locally-installed CA certificates.
 
@@ -6605,7 +6590,7 @@
       },
       'example_value': False,
       'id': 413,
-      'caption': '''Whether to enable trust in Symantec Corporation's Legacy PKI Infrastructure''',
+      'caption': '''Enable trust in Symantec Corporation's Legacy PKI Infrastructure''',
       'tags': ['system-security'],
       'desc': '''When this setting is enabled, <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> allows certificates issued by Symantec Corporation's Legacy PKI operations to be trusted if they otherwise successfully validate and chain to a recognized CA certificate.
 
@@ -7201,7 +7186,7 @@
       },
       'example_value': False,
       'id': 134,
-      'caption': '''Whether the release channel should be configurable by the user''',
+      'caption': '''Users may configure the Chrome OS release channel''',
       'tags': [],
       'desc': '''If this policy is set to True and the ChromeOsReleaseChannel policy is not specified then users of the enrolling domain will be allowed to change the release channel of the device. If this policy is set to false the device will be locked in whatever channel it was last set.
 
@@ -10369,7 +10354,7 @@
       'deprecated': True,
       'example_value': False,
       'id': 310,
-      'caption': '''Whether RC4 cipher suites in TLS are enabled''',
+      'caption': '''Enable RC4 cipher suites in TLS''',
       'tags': ['system-security'],
       'desc': '''Warning: RC4 will be completely removed from <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> after version 52 (around September 2016) and this policy will stop working then.
 
@@ -10393,7 +10378,7 @@
       'deprecated': True,
       'example_value': False,
       'id': 334,
-      'caption': '''Whether DHE cipher suites in TLS are enabled''',
+      'caption': '''Enable DHE cipher suites in TLS''',
       'tags': ['system-security'],
       'desc': '''Warning: DHE will be completely removed from <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> after version 57 (around March 2017) and this policy will stop working then.
 
diff --git a/components/sync/driver/profile_sync_service.cc b/components/sync/driver/profile_sync_service.cc
index dcabe1e..401d3992 100644
--- a/components/sync/driver/profile_sync_service.cc
+++ b/components/sync/driver/profile_sync_service.cc
@@ -508,13 +508,13 @@
         base::TimeDelta::FromSeconds(kDefaultPollIntervalSeconds);
   }
 
-  engine_->Initialize(std::move(params));
-
-  ReportPreviousSessionMemoryWarningCount();
-
   if (!IsLocalSyncEnabled()) {
     auth_manager_->ConnectionOpened();
   }
+
+  engine_->Initialize(std::move(params));
+
+  ReportPreviousSessionMemoryWarningCount();
 }
 
 void ProfileSyncService::Shutdown() {
diff --git a/components/sync/driver/sync_auth_manager.cc b/components/sync/driver/sync_auth_manager.cc
index c920362..0827896 100644
--- a/components/sync/driver/sync_auth_manager.cc
+++ b/components/sync/driver/sync_auth_manager.cc
@@ -144,6 +144,9 @@
 
 void SyncAuthManager::ConnectionOpened() {
   DCHECK(registered_for_auth_notifications_);
+  DCHECK(!connection_open_);
+
+  connection_open_ = true;
 
   // At this point, we must not already have an access token or an attempt to
   // get one.
@@ -156,6 +159,7 @@
 
 void SyncAuthManager::ConnectionStatusChanged(syncer::ConnectionStatus status) {
   DCHECK(registered_for_auth_notifications_);
+  DCHECK(connection_open_);
 
   partial_token_status_.connection_status_update_time = base::Time::Now();
   partial_token_status_.connection_status = status;
@@ -221,6 +225,8 @@
 }
 
 void SyncAuthManager::InvalidateAccessToken() {
+  DCHECK(registered_for_auth_notifications_);
+
   if (access_token_.empty()) {
     return;
   }
@@ -253,9 +259,12 @@
 
 void SyncAuthManager::ConnectionClosed() {
   DCHECK(registered_for_auth_notifications_);
+  DCHECK(connection_open_);
 
   partial_token_status_ = syncer::SyncTokenStatus();
   ClearAccessTokenAndRequest();
+
+  connection_open_ = false;
 }
 
 void SyncAuthManager::OnPrimaryAccountSet(
@@ -312,14 +321,13 @@
   if (!access_token_.empty() || request_access_token_retry_timer_.IsRunning()) {
     DCHECK(!ongoing_access_token_fetch_);
     RequestAccessToken();
-  } else if (last_auth_error_ != GoogleServiceAuthError::AuthErrorNone()) {
+  } else if (last_auth_error_ != GoogleServiceAuthError::AuthErrorNone() &&
+             connection_open_) {
     // If we were in an auth error state, then now's also a good time to try
     // again. In this case it's possible that there is already a pending
     // request, in which case RequestAccessToken will simply do nothing.
     // Note: This is necessary to get out of the "Sync paused" state (see
     // above), or to recover if the refresh token was previously removed.
-    // TODO(crbug.com/948148): This can cause us to fetch an access token even
-    // if Sync is disabled.
     RequestAccessToken();
   }
 }
@@ -374,6 +382,8 @@
 }
 
 bool SyncAuthManager::UpdateSyncAccountIfNecessary() {
+  DCHECK(registered_for_auth_notifications_);
+
   syncer::SyncAccountInfo new_account = DetermineAccountToUse();
   // If we're already using this account and its |is_primary| bit hasn't changed
   // (or there was and is no account to use), then there's nothing to do.
@@ -391,7 +401,8 @@
     sync_account_ = syncer::SyncAccountInfo();
     // Also clear any pending request or auth errors we might have, since they
     // aren't meaningful anymore.
-    ConnectionClosed();
+    partial_token_status_ = syncer::SyncTokenStatus();
+    ClearAccessTokenAndRequest();
     SetLastAuthError(GoogleServiceAuthError::AuthErrorNone());
     account_state_changed_callback_.Run();
   }
@@ -407,6 +418,9 @@
 }
 
 void SyncAuthManager::RequestAccessToken() {
+  DCHECK(registered_for_auth_notifications_);
+  DCHECK(connection_open_);
+
   // Only one active request at a time.
   if (ongoing_access_token_fetch_) {
     DCHECK(access_token_.empty());
@@ -439,6 +453,8 @@
 void SyncAuthManager::AccessTokenFetched(
     GoogleServiceAuthError error,
     identity::AccessTokenInfo access_token_info) {
+  DCHECK(registered_for_auth_notifications_);
+
   DCHECK(ongoing_access_token_fetch_);
   ongoing_access_token_fetch_.reset();
   DCHECK(!request_access_token_retry_timer_.IsRunning());
diff --git a/components/sync/driver/sync_auth_manager.h b/components/sync/driver/sync_auth_manager.h
index 574d9b3..ff49ba4 100644
--- a/components/sync/driver/sync_auth_manager.h
+++ b/components/sync/driver/sync_auth_manager.h
@@ -164,6 +164,13 @@
   GoogleServiceAuthError last_auth_error_;
   base::Time last_auth_error_time_;
 
+  // Whether Sync is currently connected to the server, i.e. ConnectionOpened()
+  // has been called, but ConnectionClosed() hasn't. While this is false, we
+  // don't try to get an access token. While it's true, we will *usually* have
+  // either an access token or a pending/scheduled request for one, but this is
+  // not guaranteed (e.g. in the case of a persistent auth error).
+  bool connection_open_ = false;
+
   // The current access token. This is mutually exclusive with
   // |ongoing_access_token_fetch_| and |request_access_token_retry_timer_|:
   // We have at most one of a) an access token OR b) a pending request OR c) a
diff --git a/components/sync/driver/sync_auth_manager_unittest.cc b/components/sync/driver/sync_auth_manager_unittest.cc
index fb733027..fbbc70c 100644
--- a/components/sync/driver/sync_auth_manager_unittest.cc
+++ b/components/sync/driver/sync_auth_manager_unittest.cc
@@ -176,6 +176,8 @@
   ASSERT_EQ(auth_manager->GetLastAuthError().state(),
             GoogleServiceAuthError::NONE);
 
+  auth_manager->ConnectionOpened();
+
   // Force an auth error by revoking the refresh token.
   identity_env()->RemoveRefreshTokenForPrimaryAccount();
   ASSERT_NE(auth_manager->GetLastAuthError().state(),
@@ -511,8 +513,8 @@
   ASSERT_EQ(auth_manager->GetLastAuthError(),
             GoogleServiceAuthError::AuthErrorNone());
 
-  // But now an invalid refresh token gets set. No new access token should get
-  // requested due to this.
+  // But now an invalid refresh token gets set, i.e. we enter the "Sync paused"
+  // state. No new access token should get requested due to this.
   base::MockCallback<base::OnceClosure> access_token_requested;
   EXPECT_CALL(access_token_requested, Run()).Times(0);
   identity_env()->SetCallbackForNextAccessTokenRequest(
@@ -526,12 +528,76 @@
           GoogleServiceAuthError::InvalidGaiaCredentialsReason::
               CREDENTIALS_REJECTED_BY_CLIENT);
   EXPECT_EQ(auth_manager->GetLastAuthError(), invalid_token_error);
+  EXPECT_TRUE(auth_manager->IsSyncPaused());
 
   // No new access token should have been requested. Since the request goes
   // through posted tasks, we have to spin the message loop.
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(SyncAuthManagerTest,
+       RequestsAccessTokenWhenInvalidRefreshTokenResolved) {
+  std::string account_id =
+      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
+  auto auth_manager = CreateAuthManager();
+  auth_manager->RegisterForAuthNotifications();
+  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
+            account_id);
+
+  // Sync starts up normally.
+  auth_manager->ConnectionOpened();
+  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      "access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
+  ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token");
+  auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK);
+  ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token");
+  ASSERT_EQ(auth_manager->GetLastAuthError(),
+            GoogleServiceAuthError::AuthErrorNone());
+
+  // But now an invalid refresh token gets set, i.e. we enter the "Sync paused"
+  // state.
+  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
+  ASSERT_TRUE(auth_manager->GetCredentials().access_token.empty());
+  ASSERT_TRUE(auth_manager->IsSyncPaused());
+
+  // Once the user signs in again and we have a valid refresh token, we should
+  // also request a new access token.
+  identity_env()->SetRefreshTokenForPrimaryAccount();
+  identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1));
+  ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token_2");
+}
+
+TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenIfSyncInactive) {
+  std::string account_id =
+      identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id;
+  auto auth_manager = CreateAuthManager();
+  auth_manager->RegisterForAuthNotifications();
+  ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id,
+            account_id);
+
+  // Sync is *not* enabled; in particular we don't call ConnectionOpened().
+
+  // An invalid refresh token gets set, i.e. we enter the "Sync paused" state
+  // (only from SyncAuthManager's point of view - Sync as a whole is still
+  // disabled).
+  identity_env()->SetInvalidRefreshTokenForPrimaryAccount();
+  ASSERT_TRUE(auth_manager->GetCredentials().access_token.empty());
+  ASSERT_TRUE(auth_manager->IsSyncPaused());
+
+  // Once the user signs in again and we have a valid refresh token, we should
+  // *not* request a new access token, since Sync isn't active.
+  base::MockCallback<base::OnceClosure> access_token_requested;
+  EXPECT_CALL(access_token_requested, Run()).Times(0);
+  identity_env()->SetCallbackForNextAccessTokenRequest(
+      access_token_requested.Get());
+  identity_env()->SetRefreshTokenForPrimaryAccount();
+
+  // Since the access token request goes through posted tasks, we have to spin
+  // the message loop to make sure it didn't happen.
+  base::RunLoop().RunUntilIdle();
+}
+
 TEST_F(SyncAuthManagerTest, IgnoresCookieJarIfFeatureDisabled) {
   base::test::ScopedFeatureList features;
   features.InitAndDisableFeature(switches::kSyncSupportSecondaryAccount);
diff --git a/components/ui_devtools/devtools_server_unittest.cc b/components/ui_devtools/devtools_server_unittest.cc
index 9ab8e73c..e75b267 100644
--- a/components/ui_devtools/devtools_server_unittest.cc
+++ b/components/ui_devtools/devtools_server_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/command_line.h"
 #include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
 #include "components/ui_devtools/switches.h"
 #include "net/base/address_list.h"
 #include "net/base/completion_once_callback.h"
@@ -17,9 +18,16 @@
 
 namespace ui_devtools {
 
+// TODO(lgrey): Hopefully temporary while we figure out why this doesn't work.
+#if defined(OS_MACOSX)
+#define MAYBE_ConnectionToViewsServer DISABLED_ConnectionToViewsServer
+#else
+#define MAYBE_ConnectionToViewsServer ConnectionToViewsServer
+#endif
+
 // Tests whether the server for Views is properly created so we can connect to
 // it.
-TEST(UIDevToolsServerTest, ConnectionToViewsServer) {
+TEST(UIDevToolsServerTest, MAYBE_ConnectionToViewsServer) {
   // Use port 80 to prevent firewall issues.
   static constexpr int fake_port = 80;
   base::CommandLine::ForCurrentProcess()->AppendSwitch(
diff --git a/components/ui_devtools/dom_agent.cc b/components/ui_devtools/dom_agent.cc
index 925d61c..8cf2d970a 100644
--- a/components/ui_devtools/dom_agent.cc
+++ b/components/ui_devtools/dom_agent.cc
@@ -71,6 +71,8 @@
                                 BuildTreeForUIElement(child));
 
   child->set_is_updating(false);
+  for (auto& observer : observers_)
+    observer.OnElementAdded(child);
 }
 
 void DOMAgent::OnUIElementReordered(UIElement* parent, UIElement* child) {
diff --git a/components/ui_devtools/dom_agent.h b/components/ui_devtools/dom_agent.h
index f8135e35..36db4a1 100644
--- a/components/ui_devtools/dom_agent.h
+++ b/components/ui_devtools/dom_agent.h
@@ -22,7 +22,8 @@
 
 class DOMAgentObserver {
  public:
-  virtual void OnElementBoundsChanged(UIElement* ui_element) = 0;
+  virtual void OnElementBoundsChanged(UIElement* ui_element) {}
+  virtual void OnElementAdded(UIElement* ui_element) {}
 };
 
 class UI_DEVTOOLS_EXPORT DOMAgent
diff --git a/components/ui_devtools/views/BUILD.gn b/components/ui_devtools/views/BUILD.gn
index 9bc16b6..99833f92 100644
--- a/components/ui_devtools/views/BUILD.gn
+++ b/components/ui_devtools/views/BUILD.gn
@@ -13,25 +13,44 @@
   sources = [
     "devtools_server_util.cc",
     "devtools_server_util.h",
-    "dom_agent_aura.cc",
-    "dom_agent_aura.h",
-    "overlay_agent_aura.cc",
-    "overlay_agent_aura.h",
+    "dom_agent_views.cc",
+    "dom_agent_views.h",
+    "overlay_agent_views.cc",
+    "overlay_agent_views.h",
     "view_element.cc",
     "view_element.h",
     "widget_element.cc",
     "widget_element.h",
-    "window_element.cc",
-    "window_element.h",
   ]
 
   deps = [
     "//components/ui_devtools",
     "//skia",
-    "//ui/aura",
     "//ui/views",
-    "//ui/wm:wm",
   ]
+
+  if (use_aura) {
+    sources += [
+      "dom_agent_aura.cc",
+      "dom_agent_aura.h",
+      "overlay_agent_aura.cc",
+      "overlay_agent_aura.h",
+      "window_element.cc",
+      "window_element.h",
+    ]
+    deps += [
+      "//ui/aura",
+      "//ui/wm:wm",
+    ]
+  }
+  if (is_mac) {
+    sources += [
+      "dom_agent_mac.h",
+      "dom_agent_mac.mm",
+      "overlay_agent_mac.h",
+      "overlay_agent_mac.mm",
+    ]
+  }
 }
 
 source_set("unit_tests") {
@@ -47,7 +66,6 @@
     "overlay_agent_unittest.cc",
     "view_element_unittest.cc",
     "widget_element_unittest.cc",
-    "window_element_unittest.cc",
   ]
 
   deps = [
@@ -56,13 +74,19 @@
     "//components/ui_devtools:test_support",
     "//skia",
     "//testing/gtest",
-    "//ui/aura",
-    "//ui/aura:test_support",
     "//ui/events:test_support",
     "//ui/views",
     "//ui/views:test_support",
-    "//ui/wm:wm",
   ]
 
+  if (use_aura) {
+    sources += [ "window_element_unittest.cc" ]
+    deps += [
+      "//ui/aura",
+      "//ui/aura:test_support",
+      "//ui/wm:wm",
+    ]
+  }
+
   configs += [ "//build/config:precompiled_headers" ]
 }
diff --git a/components/ui_devtools/views/devtools_server_util.cc b/components/ui_devtools/views/devtools_server_util.cc
index c04fe834..aaadddd 100644
--- a/components/ui_devtools/views/devtools_server_util.cc
+++ b/components/ui_devtools/views/devtools_server_util.cc
@@ -7,13 +7,19 @@
 #include <memory>
 
 #include "base/command_line.h"
+#include "build/build_config.h"
 #include "components/ui_devtools/css_agent.h"
 #include "components/ui_devtools/devtools_server.h"
 #include "components/ui_devtools/switches.h"
+#include "components/ui_devtools/views/dom_agent_views.h"
+#include "components/ui_devtools/views/overlay_agent_views.h"
+
+#if defined(USE_AURA)
 #include "components/ui_devtools/views/dom_agent_aura.h"
 #include "components/ui_devtools/views/overlay_agent_aura.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
+#endif
 
 namespace ui_devtools {
 
@@ -26,16 +32,16 @@
   DCHECK(server);
   auto client =
       std::make_unique<UiDevToolsClient>("UiDevToolsClient", server.get());
-  aura::Env* env = aura::Env::GetInstance();
-  auto dom_agent_aura = std::make_unique<DOMAgentAura>(env);
-  auto* dom_agent_aura_ptr = dom_agent_aura.get();
-  client->AddAgent(std::move(dom_agent_aura));
-  client->AddAgent(std::make_unique<CSSAgent>(dom_agent_aura_ptr));
-  client->AddAgent(std::make_unique<OverlayAgentAura>(dom_agent_aura_ptr, env));
+  auto dom_agent_views = DOMAgentViews::Create();
+  auto* dom_agent_views_ptr = dom_agent_views.get();
+  client->AddAgent(std::move(dom_agent_views));
+  client->AddAgent(std::make_unique<CSSAgent>(dom_agent_views_ptr));
+  client->AddAgent(OverlayAgentViews::Create(dom_agent_views_ptr));
   server->AttachClient(std::move(client));
   return server;
 }
 
+#if defined(USE_AURA)
 void RegisterAdditionalRootWindowsAndEnv(std::vector<aura::Window*> roots) {
   DCHECK(!roots.empty());
   OverlayAgentAura::GetInstance()->RegisterEnv(roots[0]->env());
@@ -43,5 +49,6 @@
   for (auto* root : roots)
     DOMAgentAura::GetInstance()->RegisterRootWindow(root);
 }
+#endif
 
 }  // namespace ui_devtools
diff --git a/components/ui_devtools/views/dom_agent_aura.cc b/components/ui_devtools/views/dom_agent_aura.cc
index db17684b..46cbe030 100644
--- a/components/ui_devtools/views/dom_agent_aura.cc
+++ b/components/ui_devtools/views/dom_agent_aura.cc
@@ -1,48 +1,30 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright 2019 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 "components/ui_devtools/views/dom_agent_aura.h"
 
-#include <memory>
-#include <utility>
-#include <vector>
-
 #include "base/stl_util.h"
-#include "components/ui_devtools/devtools_server.h"
-#include "components/ui_devtools/root_element.h"
-#include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/views/view_element.h"
 #include "components/ui_devtools/views/widget_element.h"
 #include "components/ui_devtools/views/window_element.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-#include "ui/wm/core/window_util.h"
 
 namespace ui_devtools {
+
 namespace {
-
-using ui_devtools::protocol::DOM::Node;
 using ui_devtools::protocol::Array;
-// TODO(mhashmi): Make ids reusable
-
-views::Widget* GetWidgetFromWindow(gfx::NativeWindow window) {
-  return views::Widget::GetWidgetForNativeView(window);
-}
-
+using ui_devtools::protocol::DOM::Node;
 }  // namespace
 
 DOMAgentAura* DOMAgentAura::dom_agent_aura_ = nullptr;
 
-DOMAgentAura::DOMAgentAura(aura::Env* env) {
+DOMAgentAura::DOMAgentAura() {
   DCHECK(!dom_agent_aura_);
   dom_agent_aura_ = this;
-  RegisterEnv(env);
+  RegisterEnv(aura::Env::GetInstance());
 }
-
 DOMAgentAura::~DOMAgentAura() {
   for (aura::Window* window : roots_)
     window->RemoveObserver(this);
@@ -78,41 +60,26 @@
   return children;
 }
 
-std::unique_ptr<Node> DOMAgentAura::BuildTreeForUIElement(
-    UIElement* ui_element) {
-  if (ui_element->type() == UIElementType::WINDOW) {
-    return BuildTreeForWindow(
-        ui_element,
-        UIElement::GetBackingElement<aura::Window, WindowElement>(ui_element));
-  } else if (ui_element->type() == UIElementType::WIDGET) {
-    return BuildTreeForRootWidget(
-        ui_element,
-        UIElement::GetBackingElement<views::Widget, WidgetElement>(ui_element));
-  } else if (ui_element->type() == UIElementType::VIEW) {
-    return BuildTreeForView(
-        ui_element,
-        UIElement::GetBackingElement<views::View, ViewElement>(ui_element));
-  }
-  return nullptr;
-}
-
 std::unique_ptr<Node> DOMAgentAura::BuildTreeForWindow(
-    UIElement* window_element_root,
-    aura::Window* window) {
+    UIElement* window_element_root) {
+  DCHECK(window_element_root->type() == UIElementType::WINDOW);
+  aura::Window* window =
+      UIElement::GetBackingElement<aura::Window, WindowElement>(
+          window_element_root);
   std::unique_ptr<Array<Node>> children = Array<Node>::create();
-  views::Widget* widget = GetWidgetFromWindow(window);
+  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
   if (widget) {
     UIElement* widget_element =
         new WidgetElement(widget, this, window_element_root);
 
-    children->addItem(BuildTreeForRootWidget(widget_element, widget));
+    children->addItem(BuildTreeForRootWidget(widget_element));
     window_element_root->AddChild(widget_element);
   }
   for (aura::Window* child : window->children()) {
     UIElement* window_element =
         new WindowElement(child, this, window_element_root);
 
-    children->addItem(BuildTreeForWindow(window_element, child));
+    children->addItem(BuildTreeForWindow(window_element));
     window_element_root->AddChild(window_element);
   }
   std::unique_ptr<Node> node =
@@ -121,49 +88,9 @@
   return node;
 }
 
-std::unique_ptr<Node> DOMAgentAura::BuildTreeForRootWidget(
-    UIElement* widget_element,
-    views::Widget* widget) {
-  std::unique_ptr<Array<Node>> children = Array<Node>::create();
-
-  UIElement* view_element =
-      new ViewElement(widget->GetRootView(), this, widget_element);
-
-  children->addItem(BuildTreeForView(view_element, widget->GetRootView()));
-  widget_element->AddChild(view_element);
-
-  std::unique_ptr<Node> node =
-      BuildNode("Widget", widget_element->GetAttributes(), std::move(children),
-                widget_element->node_id());
-  return node;
-}
-
-std::unique_ptr<Node> DOMAgentAura::BuildTreeForView(UIElement* view_element,
-                                                     views::View* view) {
-  std::unique_ptr<Array<Node>> children = Array<Node>::create();
-
-  for (auto* child : view->GetChildrenInZOrder()) {
-    // When building the subtree, a particular view could be visited multiple
-    // times because for each view of the subtree, we would call
-    // BuildTreeForView(..) on that view which causes the subtree with that view
-    // as root being visited again.  Here we check if we already constructed the
-    // ViewElement and skip true.
-    UIElement* view_element_child = nullptr;
-    auto id =
-        view_element->FindUIElementIdForBackendElement<views::View>(child);
-    if (id > 0) {
-      view_element_child = GetElementFromNodeId(id);
-    } else {
-      view_element_child = new ViewElement(child, this, view_element);
-      view_element->AddChild(view_element_child);
-    }
-
-    children->addItem(BuildTreeForView(view_element_child, child));
-  }
-  std::unique_ptr<Node> node =
-      BuildNode("View", view_element->GetAttributes(), std::move(children),
-                view_element->node_id());
-  return node;
+// static
+std::unique_ptr<DOMAgentViews> DOMAgentViews::Create() {
+  return std::make_unique<DOMAgentAura>();
 }
 
 }  // namespace ui_devtools
diff --git a/components/ui_devtools/views/dom_agent_aura.h b/components/ui_devtools/views/dom_agent_aura.h
index 39eb97b..c92e07a 100644
--- a/components/ui_devtools/views/dom_agent_aura.h
+++ b/components/ui_devtools/views/dom_agent_aura.h
@@ -5,34 +5,32 @@
 #ifndef COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_AURA_H_
 #define COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_AURA_H_
 
-#include "components/ui_devtools/DOM.h"
-#include "components/ui_devtools/dom_agent.h"
+#include "components/ui_devtools/views/dom_agent_views.h"
+
 #include "ui/aura/env_observer.h"
 #include "ui/aura/window_observer.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
 
 namespace aura {
 class Env;
 class Window;
-}
+}  // namespace aura
 
 namespace ui_devtools {
 
-class DOMAgentAura : public DOMAgent,
+class DOMAgentAura : public DOMAgentViews,
                      public aura::EnvObserver,
                      public aura::WindowObserver {
  public:
-  explicit DOMAgentAura(aura::Env* env);
+  DOMAgentAura();
+
   ~DOMAgentAura() override;
-
   static DOMAgentAura* GetInstance() { return dom_agent_aura_; }
-
   void RegisterEnv(aura::Env* env);
   void RegisterRootWindow(aura::Window* root);
-  const std::vector<aura::Window*>& root_windows() const { return roots_; }
 
- private:
+  // DOMAgent
+  std::vector<UIElement*> CreateChildrenForRoot() override;
+
   // aura::EnvObserver:
   void OnWindowInitialized(aura::Window* window) override {}
   void OnHostInitialized(aura::WindowTreeHost* host) override;
@@ -41,18 +39,9 @@
   void OnWindowDestroying(aura::Window* window) override;
 
   std::unique_ptr<protocol::DOM::Node> BuildTreeForWindow(
-      UIElement* window_element_root,
-      aura::Window* window);
-  std::unique_ptr<protocol::DOM::Node> BuildTreeForRootWidget(
-      UIElement* widget_element,
-      views::Widget* widget);
-  std::unique_ptr<protocol::DOM::Node> BuildTreeForView(UIElement* view_element,
-                                                        views::View* view);
+      UIElement* window_element_root) override;
 
-  std::vector<UIElement*> CreateChildrenForRoot() override;
-  std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement(
-      UIElement* ui_element) override;
-
+ private:
   static DOMAgentAura* dom_agent_aura_;
 
   std::vector<aura::Env*> envs_;
@@ -60,7 +49,6 @@
 
   DISALLOW_COPY_AND_ASSIGN(DOMAgentAura);
 };
-
 }  // namespace ui_devtools
 
 #endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_AURA_H_
diff --git a/components/ui_devtools/views/dom_agent_mac.h b/components/ui_devtools/views/dom_agent_mac.h
new file mode 100644
index 0000000..863d882
--- /dev/null
+++ b/components/ui_devtools/views/dom_agent_mac.h
@@ -0,0 +1,47 @@
+// Copyright 2019 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 COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_MAC_H_
+#define COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_MAC_H_
+
+#include "components/ui_devtools/views/dom_agent_views.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+class NativeWidgetMac;
+}
+
+namespace ui_devtools {
+
+class DOMAgentMac : public DOMAgentViews, public views::WidgetObserver {
+ public:
+  DOMAgentMac();
+  ~DOMAgentMac() override;
+
+  void OnNativeWidgetAdded(views::NativeWidgetMac* native_widget);
+
+  // DOMAgent
+  std::vector<UIElement*> CreateChildrenForRoot() override;
+
+  // DevTools protocol generated backend classes.
+  protocol::Response enable() override;
+  protocol::Response disable() override;
+
+  // views::WidgetObserver
+  void OnWidgetDestroying(views::Widget* widget) override;
+
+  // DOMAgentViews
+  std::unique_ptr<protocol::DOM::Node> BuildTreeForWindow(
+      UIElement* window_element_root) override;
+
+ private:
+  void InitializeRootsFromOpenWindows();
+
+  std::vector<views::Widget*> roots_;
+
+  DISALLOW_COPY_AND_ASSIGN(DOMAgentMac);
+};
+}  // namespace ui_devtools
+
+#endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_MAC_H_
diff --git a/components/ui_devtools/views/dom_agent_mac.mm b/components/ui_devtools/views/dom_agent_mac.mm
new file mode 100644
index 0000000..10d5502
--- /dev/null
+++ b/components/ui_devtools/views/dom_agent_mac.mm
@@ -0,0 +1,78 @@
+// Copyright 2019 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 "components/ui_devtools/views/dom_agent_mac.h"
+
+#import <AppKit/AppKit.h>
+
+#include "components/ui_devtools/views/widget_element.h"
+#include "ui/views/widget/native_widget_mac.h"
+
+namespace ui_devtools {
+
+DOMAgentMac::DOMAgentMac() {}
+DOMAgentMac::~DOMAgentMac() {
+  for (views::Widget* widget : roots_)
+    widget->RemoveObserver(this);
+}
+
+protocol::Response DOMAgentMac::enable() {
+  views::NativeWidgetMac::SetInitNativeWidgetCallback(base::BindRepeating(
+      &DOMAgentMac::OnNativeWidgetAdded, base::Unretained(this)));
+  return DOMAgent::enable();
+}
+
+protocol::Response DOMAgentMac::disable() {
+  views::NativeWidgetMac::SetInitNativeWidgetCallback(
+      base::RepeatingCallback<void(views::NativeWidgetMac*)>());
+  return DOMAgent::disable();
+}
+
+std::vector<UIElement*> DOMAgentMac::CreateChildrenForRoot() {
+  if (roots_.size() == 0)
+    InitializeRootsFromOpenWindows();
+
+  std::vector<UIElement*> children;
+  for (views::Widget* widget : roots_) {
+    UIElement* widget_element = new WidgetElement(widget, this, element_root());
+    children.push_back(widget_element);
+  }
+  return children;
+}
+
+void DOMAgentMac::OnWidgetDestroying(views::Widget* widget) {
+  roots_.erase(std::find(roots_.begin(), roots_.end(), widget), roots_.end());
+}
+
+void DOMAgentMac::OnNativeWidgetAdded(views::NativeWidgetMac* native_widget) {
+  views::Widget* widget = native_widget->GetWidget();
+  DCHECK(widget);
+  roots_.push_back(widget);
+  UIElement* widget_element = new WidgetElement(widget, this, element_root());
+  element_root()->AddChild(widget_element);
+}
+
+std::unique_ptr<protocol::DOM::Node> DOMAgentMac::BuildTreeForWindow(
+    UIElement* window_element_root) {
+  // Window elements aren't supported on Mac.
+  NOTREACHED();
+  return nullptr;
+}
+
+void DOMAgentMac::InitializeRootsFromOpenWindows() {
+  for (NSWindow* window : [NSApp windows]) {
+    if (views::Widget* widget =
+            views::Widget::GetWidgetForNativeWindow(window)) {
+      widget->AddObserver(this);
+      roots_.push_back(widget);
+    }
+  }
+}
+
+// static
+std::unique_ptr<DOMAgentViews> DOMAgentViews::Create() {
+  return std::make_unique<DOMAgentMac>();
+}
+
+}  // namespace ui_devtools
diff --git a/components/ui_devtools/views/dom_agent_unittest.cc b/components/ui_devtools/views/dom_agent_unittest.cc
index 58da7bb9..3c42fee 100644
--- a/components/ui_devtools/views/dom_agent_unittest.cc
+++ b/components/ui_devtools/views/dom_agent_unittest.cc
@@ -4,14 +4,14 @@
 
 #include <memory>
 
-#include "components/ui_devtools/views/dom_agent_aura.h"
+#include "components/ui_devtools/views/dom_agent_views.h"
 
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/ui_devtools/css_agent.h"
 #include "components/ui_devtools/ui_devtools_unittest_utils.h"
 #include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/views/overlay_agent_aura.h"
+#include "components/ui_devtools/views/overlay_agent_views.h"
 #include "components/ui_devtools/views/view_element.h"
 #include "components/ui_devtools/views/widget_element.h"
 #include "ui/views/test/views_test_base.h"
@@ -75,7 +75,9 @@
     views::Widget* widget = new views::Widget;
     views::Widget::InitParams params;
     params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET;
+#if defined(USE_AURA)
     params.parent = GetContext();
+#endif
     widget->Init(params);
     return widget->native_widget_private();
   }
@@ -86,12 +88,15 @@
     params.delegate = nullptr;
     params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
     params.bounds = bounds;
+#if defined(USE_AURA)
     params.parent = GetContext();
+#endif
     widget->Init(params);
     widget->Show();
     return widget;
   }
 
+#if defined(USE_AURA)
   std::unique_ptr<aura::Window> CreateChildWindow(
       aura::Window* parent,
       aura::client::WindowType type = aura::client::WINDOW_TYPE_NORMAL) {
@@ -103,18 +108,18 @@
     window->Show();
     return window;
   }
+#endif
 
   void SetUp() override {
     fake_frontend_channel_ = std::make_unique<FakeFrontendChannel>();
     uber_dispatcher_ =
         std::make_unique<UberDispatcher>(fake_frontend_channel_.get());
-    aura::Env* env = aura::Env::GetInstance();
-    dom_agent_ = std::make_unique<DOMAgentAura>(env);
+    dom_agent_ = DOMAgentViews::Create();
     dom_agent_->Init(uber_dispatcher_.get());
     css_agent_ = std::make_unique<CSSAgent>(dom_agent_.get());
     css_agent_->Init(uber_dispatcher_.get());
     css_agent_->enable();
-    overlay_agent_ = std::make_unique<OverlayAgentAura>(dom_agent_.get(), env);
+    overlay_agent_ = OverlayAgentViews::Create(dom_agent_.get());
     overlay_agent_->Init(uber_dispatcher_.get());
     overlay_agent_->enable();
 
@@ -122,11 +127,15 @@
     // WindowTreeHosts in ViewTestBase::SetUp().
     views::ViewsTestBase::SetUp();
 
+#if defined(USE_AURA)
     top_window = CreateChildWindow(GetContext());
+#endif
   }
 
   void TearDown() override {
+#if defined(USE_AURA)
     top_window.reset();
+#endif
     css_agent_.reset();
     overlay_agent_.reset();
     dom_agent_.reset();
@@ -239,16 +248,17 @@
   FakeFrontendChannel* frontend_channel() {
     return fake_frontend_channel_.get();
   }
-  DOMAgentAura* dom_agent() { return dom_agent_.get(); }
+  DOMAgentViews* dom_agent() { return dom_agent_.get(); }
 
+#if defined(USE_AURA)
   std::unique_ptr<aura::Window> top_window;
-
+#endif
  private:
   std::unique_ptr<UberDispatcher> uber_dispatcher_;
   std::unique_ptr<FakeFrontendChannel> fake_frontend_channel_;
-  std::unique_ptr<DOMAgentAura> dom_agent_;
+  std::unique_ptr<DOMAgentViews> dom_agent_;
   std::unique_ptr<CSSAgent> css_agent_;
-  std::unique_ptr<OverlayAgentAura> overlay_agent_;
+  std::unique_ptr<OverlayAgentViews> overlay_agent_;
 
   DISALLOW_COPY_AND_ASSIGN(DOMAgentTest);
 };
@@ -263,7 +273,7 @@
   //        child_view
   //   child_window
   std::unique_ptr<views::Widget> widget(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
   aura::Window* parent_window = widget->GetNativeWindow();
   parent_window->SetName("parent_window");
   std::unique_ptr<aura::Window> child_window = CreateChildWindow(parent_window);
@@ -317,10 +327,9 @@
   //          child_b121
   //          child_b122
   std::unique_ptr<views::Widget> widget_a(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
   std::unique_ptr<views::Widget> widget_b(
-      CreateTestWidget(gfx::Rect(100, 100, 1, 1)));
-
+      CreateTestWidget(gfx::Rect(100, 100, 80, 80)));
   widget_a->GetRootView()->AddChildView(new TestView("child_a1"));
   widget_a->GetRootView()->AddChildView(new TestView("child_a2"));
 
@@ -447,7 +456,7 @@
 
 TEST_F(DOMAgentTest, ViewInserted) {
   std::unique_ptr<views::Widget> widget(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
   widget->Show();
 
   // Initialize DOMAgent
@@ -461,7 +470,7 @@
 
 TEST_F(DOMAgentTest, ViewRemoved) {
   std::unique_ptr<views::Widget> widget(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
   widget->Show();
   views::View* root_view = widget->GetRootView();
 
@@ -481,7 +490,7 @@
 
 TEST_F(DOMAgentTest, ViewRearranged) {
   std::unique_ptr<views::Widget> widget(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
 
   widget->Show();
   views::View* root_view = widget->GetRootView();
@@ -521,7 +530,7 @@
 
 TEST_F(DOMAgentTest, ViewRearrangedRemovedAndInserted) {
   std::unique_ptr<views::Widget> widget(
-      CreateTestWidget(gfx::Rect(1, 1, 1, 1)));
+      CreateTestWidget(gfx::Rect(1, 1, 80, 80)));
 
   widget->Show();
   views::View* root_view = widget->GetRootView();
diff --git a/components/ui_devtools/views/dom_agent_views.cc b/components/ui_devtools/views/dom_agent_views.cc
new file mode 100644
index 0000000..31e6c96
--- /dev/null
+++ b/components/ui_devtools/views/dom_agent_views.cc
@@ -0,0 +1,92 @@
+// 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 "components/ui_devtools/views/dom_agent_views.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "components/ui_devtools/devtools_server.h"
+#include "components/ui_devtools/root_element.h"
+#include "components/ui_devtools/ui_element.h"
+#include "components/ui_devtools/views/view_element.h"
+#include "components/ui_devtools/views/widget_element.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ui_devtools {
+namespace {
+
+using ui_devtools::protocol::Array;
+using ui_devtools::protocol::DOM::Node;
+
+}  // namespace
+
+DOMAgentViews::DOMAgentViews() {}
+DOMAgentViews::~DOMAgentViews() {}
+
+std::unique_ptr<Node> DOMAgentViews::BuildTreeForUIElement(
+    UIElement* ui_element) {
+  if (ui_element->type() == UIElementType::WINDOW) {
+    return BuildTreeForWindow(ui_element);
+  } else if (ui_element->type() == UIElementType::WIDGET) {
+    return BuildTreeForRootWidget(ui_element);
+  } else if (ui_element->type() == UIElementType::VIEW) {
+    return BuildTreeForView(ui_element);
+  }
+  return nullptr;
+}
+
+std::unique_ptr<Node> DOMAgentViews::BuildTreeForRootWidget(
+    UIElement* widget_element) {
+  DCHECK(widget_element->type() == UIElementType::WIDGET);
+  views::Widget* widget =
+      UIElement::GetBackingElement<views::Widget, WidgetElement>(
+          widget_element);
+  std::unique_ptr<Array<Node>> children = Array<Node>::create();
+
+  UIElement* view_element =
+      new ViewElement(widget->GetRootView(), this, widget_element);
+
+  children->addItem(BuildTreeForView(view_element));
+  widget_element->AddChild(view_element);
+
+  std::unique_ptr<Node> node =
+      BuildNode("Widget", widget_element->GetAttributes(), std::move(children),
+                widget_element->node_id());
+  return node;
+}
+
+std::unique_ptr<Node> DOMAgentViews::BuildTreeForView(UIElement* view_element) {
+  DCHECK(view_element->type() == UIElementType::VIEW);
+  views::View* view =
+      UIElement::GetBackingElement<views::View, ViewElement>(view_element);
+  std::unique_ptr<Array<Node>> children = Array<Node>::create();
+
+  for (auto* child : view->GetChildrenInZOrder()) {
+    // When building the subtree, a particular view could be visited multiple
+    // times because for each view of the subtree, we would call
+    // BuildTreeForView(..) on that view which causes the subtree with that view
+    // as root being visited again.  Here we check if we already constructed the
+    // ViewElement and skip true.
+    UIElement* view_element_child = nullptr;
+    auto id =
+        view_element->FindUIElementIdForBackendElement<views::View>(child);
+    if (id > 0) {
+      view_element_child = GetElementFromNodeId(id);
+    } else {
+      view_element_child = new ViewElement(child, this, view_element);
+      view_element->AddChild(view_element_child);
+    }
+
+    children->addItem(BuildTreeForView(view_element_child));
+  }
+  std::unique_ptr<Node> node =
+      BuildNode("View", view_element->GetAttributes(), std::move(children),
+                view_element->node_id());
+  return node;
+}
+
+}  // namespace ui_devtools
diff --git a/components/ui_devtools/views/dom_agent_views.h b/components/ui_devtools/views/dom_agent_views.h
new file mode 100644
index 0000000..e0c7acc
--- /dev/null
+++ b/components/ui_devtools/views/dom_agent_views.h
@@ -0,0 +1,37 @@
+// 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 COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_VIEWS_H_
+#define COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_VIEWS_H_
+
+#include "components/ui_devtools/DOM.h"
+#include "components/ui_devtools/dom_agent.h"
+
+namespace ui_devtools {
+
+class DOMAgentViews : public DOMAgent {
+ public:
+  ~DOMAgentViews() override;
+  static std::unique_ptr<DOMAgentViews> Create();
+
+ protected:
+  DOMAgentViews();
+
+  virtual std::unique_ptr<protocol::DOM::Node> BuildTreeForWindow(
+      UIElement* window_element_root) = 0;
+  std::unique_ptr<protocol::DOM::Node> BuildTreeForRootWidget(
+      UIElement* widget_element);
+  std::unique_ptr<protocol::DOM::Node> BuildTreeForView(
+      UIElement* view_element);
+
+  std::unique_ptr<protocol::DOM::Node> BuildTreeForUIElement(
+      UIElement* ui_element) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DOMAgentViews);
+};
+
+}  // namespace ui_devtools
+
+#endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_DOM_AGENT_VIEWS_H_
diff --git a/components/ui_devtools/views/overlay_agent_aura.cc b/components/ui_devtools/views/overlay_agent_aura.cc
index d7c5e6a..a5953b2 100644
--- a/components/ui_devtools/views/overlay_agent_aura.cc
+++ b/components/ui_devtools/views/overlay_agent_aura.cc
@@ -4,381 +4,21 @@
 
 #include "components/ui_devtools/views/overlay_agent_aura.h"
 
-#include "base/strings/utf_string_conversions.h"
-#include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/views/view_element.h"
-#include "components/ui_devtools/views/widget_element.h"
 #include "components/ui_devtools/views/window_element.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "third_party/skia/include/effects/SkDashPathEffect.h"
 #include "ui/aura/env.h"
-#include "ui/compositor/paint_recorder.h"
-#include "ui/display/display.h"
-#include "ui/display/screen.h"
-#include "ui/events/event.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gfx/render_text.h"
-#include "ui/views/background.h"
-#include "ui/views/border.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/window_util.h"
 
 namespace ui_devtools {
 
-namespace {
-
-using namespace ui_devtools::protocol;
-
-void DrawRulerText(const base::string16& utf16_text,
-                   const gfx::Point& p,
-                   gfx::Canvas* canvas,
-                   gfx::RenderText* render_text_) {
-  render_text_->SetText(utf16_text);
-  render_text_->SetColor(SK_ColorRED);
-  const gfx::Rect text_rect(gfx::Rect(p, render_text_->GetStringSize()));
-  canvas->FillRect(text_rect, SK_ColorWHITE, SkBlendMode::kColor);
-  render_text_->SetDisplayRect(text_rect);
-  render_text_->Draw(canvas);
-}
-
-void DrawRulers(const gfx::Rect& screen_bounds,
-                gfx::Canvas* canvas,
-                gfx::RenderText* render_text_) {
-  // Top horizontal ruler from left to right.
-  canvas->Draw1pxLine(gfx::PointF(0.0f, 0.0f),
-                      gfx::PointF(screen_bounds.right(), 0.0f),
-                      SK_ColorMAGENTA);
-
-  // Left veritical ruler from top to bottom.
-  canvas->Draw1pxLine(gfx::PointF(0.0f, 0.0f),
-                      gfx::PointF(0.0f, screen_bounds.bottom()),
-                      SK_ColorMAGENTA);
-
-  int short_stroke = 5;
-  int long_stroke = 10;
-  int gap_between_strokes = 4;
-  int gap_between_long_stroke = 100;
-
-  // Draw top horizontal ruler.
-  for (int x = gap_between_strokes; x < screen_bounds.right();
-       x += gap_between_strokes) {
-    if (x % gap_between_long_stroke == 0) {
-      canvas->Draw1pxLine(gfx::PointF(x, 0.0f), gfx::PointF(x, long_stroke),
-                          SK_ColorMAGENTA);
-      // Draw ruler marks.
-      base::string16 utf16_text = base::UTF8ToUTF16(std::to_string(x));
-      DrawRulerText(utf16_text, gfx::Point(x + 2, long_stroke), canvas,
-                    render_text_);
-
-    } else {
-      canvas->Draw1pxLine(gfx::PointF(x, 0.0f), gfx::PointF(x, short_stroke),
-                          SK_ColorMAGENTA);
-    }
-  }
-
-  // Draw left vertical ruler.
-  for (int y = 0; y < screen_bounds.bottom(); y += gap_between_strokes) {
-    if (y % gap_between_long_stroke == 0) {
-      canvas->Draw1pxLine(gfx::PointF(0.0f, y), gfx::PointF(long_stroke, y),
-                          SK_ColorMAGENTA);
-      // Draw ruler marks.
-      base::string16 utf16_text = base::UTF8ToUTF16(std::to_string(y));
-      DrawRulerText(utf16_text, gfx::Point(short_stroke + 1, y + 2), canvas,
-                    render_text_);
-    } else {
-      canvas->Draw1pxLine(gfx::PointF(0.0f, y), gfx::PointF(short_stroke, y),
-                          SK_ColorMAGENTA);
-    }
-  }
-}
-
-// Draw width() x height() of a rectangle if not empty. Otherwise, draw either
-// width() or height() if any of them is not empty.
-void DrawSizeOfRectangle(const gfx::Rect& hovered_rect,
-                         const RectSide drawing_side,
-                         gfx::Canvas* canvas,
-                         gfx::RenderText* render_text_) {
-  base::string16 utf16_text;
-  const std::string unit = "dp";
-
-  if (!hovered_rect.IsEmpty()) {
-    utf16_text = base::UTF8ToUTF16(hovered_rect.size().ToString() + unit);
-  } else if (hovered_rect.height()) {
-    // Draw only height() if height() is not empty.
-    utf16_text =
-        base::UTF8ToUTF16(std::to_string(hovered_rect.height()) + unit);
-  } else if (hovered_rect.width()) {
-    // Draw only width() if width() is not empty.
-    utf16_text = base::UTF8ToUTF16(std::to_string(hovered_rect.width()) + unit);
-  } else {
-    // If both width() and height() are empty, canvas won't draw size.
-    return;
-  }
-  render_text_->SetText(utf16_text);
-  render_text_->SetColor(SK_ColorRED);
-
-  const gfx::Size& text_size = render_text_->GetStringSize();
-  gfx::Rect text_rect;
-  if (drawing_side == RectSide::LEFT_SIDE) {
-    const gfx::Point text_left_side(
-        hovered_rect.x() + 1,
-        hovered_rect.height() / 2 - text_size.height() / 2 + hovered_rect.y());
-    text_rect = gfx::Rect(text_left_side,
-                          gfx::Size(text_size.width(), text_size.height()));
-  } else if (drawing_side == RectSide::RIGHT_SIDE) {
-    const gfx::Point text_right_side(
-        hovered_rect.right() - 1 - text_size.width(),
-        hovered_rect.height() / 2 - text_size.height() / 2 + hovered_rect.y());
-    text_rect = gfx::Rect(text_right_side,
-                          gfx::Size(text_size.width(), text_size.height()));
-  } else if (drawing_side == RectSide::TOP_SIDE) {
-    const gfx::Point text_top_side(
-        hovered_rect.x() + hovered_rect.width() / 2 - text_size.width() / 2,
-        hovered_rect.y() + 1);
-    text_rect = gfx::Rect(text_top_side,
-                          gfx::Size(text_size.width(), text_size.height()));
-  } else if (drawing_side == RectSide::BOTTOM_SIDE) {
-    const gfx::Point text_top_side(
-        hovered_rect.x() + hovered_rect.width() / 2 - text_size.width() / 2,
-        hovered_rect.bottom() - 1 - text_size.height());
-    text_rect = gfx::Rect(text_top_side,
-                          gfx::Size(text_size.width(), text_size.height()));
-  }
-  canvas->FillRect(text_rect, SK_ColorWHITE, SkBlendMode::kColor);
-  render_text_->SetDisplayRect(text_rect);
-  render_text_->Draw(canvas);
-}
-
-void DrawRectGuideLinesOnCanvas(const gfx::Rect& screen_bounds,
-                                const gfx::RectF& rect_f,
-                                cc::PaintFlags flags,
-                                gfx::Canvas* canvas) {
-  // Top horizontal dotted line from left to right.
-  canvas->DrawLine(gfx::PointF(0.0f, rect_f.y()),
-                   gfx::PointF(screen_bounds.right(), rect_f.y()), flags);
-
-  // Bottom horizontal dotted line from left to right.
-  canvas->DrawLine(gfx::PointF(0.0f, rect_f.bottom()),
-                   gfx::PointF(screen_bounds.right(), rect_f.bottom()), flags);
-
-  // Left vertical dotted line from top to bottom.
-  canvas->DrawLine(gfx::PointF(rect_f.x(), 0.0f),
-                   gfx::PointF(rect_f.x(), screen_bounds.bottom()), flags);
-
-  // Right vertical dotted line from top to bottom.
-  canvas->DrawLine(gfx::PointF(rect_f.right(), 0.0f),
-                   gfx::PointF(rect_f.right(), screen_bounds.bottom()), flags);
-}
-
-void DrawSizeWithAnyBounds(float x1,
-                           float y1,
-                           float x2,
-                           float y2,
-                           RectSide side,
-                           gfx::Canvas* canvas,
-                           gfx::RenderText* render_text) {
-  if (x2 > x1 || y2 > y1) {
-    DrawSizeOfRectangle(gfx::Rect(x1, y1, x2 - x1, y2 - y1), side, canvas,
-                        render_text);
-  } else {
-    DrawSizeOfRectangle(gfx::Rect(x2, y2, x1 - x2, y1 - y2), side, canvas,
-                        render_text);
-  }
-}
-
-void DrawR1ContainsR2(const gfx::RectF& pinned_rect_f,
-                      const gfx::RectF& hovered_rect_f,
-                      const cc::PaintFlags& flags,
-                      gfx::Canvas* canvas,
-                      gfx::RenderText* render_text) {
-  // Horizontal left distance line.
-  float x1 = pinned_rect_f.x();
-  float y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
-  float x2 = hovered_rect_f.x();
-  float y2 = y1;
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-
-  // Horizontal right distance line.
-  x1 = hovered_rect_f.right();
-  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
-  x2 = pinned_rect_f.right();
-  y2 = y1;
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-
-  // Vertical top distance line.
-  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
-  y1 = pinned_rect_f.y();
-  x2 = x1;
-  y2 = hovered_rect_f.y();
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-
-  // Vertical bottom distance line.
-  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
-  y1 = hovered_rect_f.bottom();
-  x2 = x1;
-  y2 = pinned_rect_f.bottom();
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-}
-
-void DrawR1HorizontalFullLeftR2(const gfx::RectF& pinned_rect_f,
-                                const gfx::RectF& hovered_rect_f,
-                                const cc::PaintFlags& flags,
-                                gfx::Canvas* canvas,
-                                gfx::RenderText* render_text) {
-  // Horizontal left distance line.
-  float x1 = hovered_rect_f.right();
-  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
-  float x2 = pinned_rect_f.x();
-  float y2 = y1;
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-}
-
-void DrawR1TopFullLeftR2(const gfx::RectF& pinned_rect_f,
-                         const gfx::RectF& hovered_rect_f,
-                         const cc::PaintFlags& flags,
-                         gfx::Canvas* canvas_,
-                         gfx::RenderText* render_text) {
-  float x1 = hovered_rect_f.x() + hovered_rect_f.width();
-  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
-  float x2 = pinned_rect_f.x();
-  float y2 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
-
-  // Horizontal left dotted line.
-  canvas_->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas_,
-                        render_text);
-  x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
-  y1 = hovered_rect_f.y() + hovered_rect_f.height();
-  x2 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
-  y2 = pinned_rect_f.y();
-
-  // Vertical left dotted line.
-  canvas_->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas_,
-                        render_text);
-}
-
-void DrawR1BottomFullLeftR2(const gfx::RectF& pinned_rect_f,
-                            const gfx::RectF& hovered_rect_f,
-                            const cc::PaintFlags& flags,
-                            gfx::Canvas* canvas,
-                            gfx::RenderText* render_text) {
-  float x1 = hovered_rect_f.right();
-  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
-  float x2 = pinned_rect_f.x();
-  float y2 = y1;
-
-  // Horizontal left distance line.
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-
-  x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
-  y1 = pinned_rect_f.bottom();
-  x2 = x1;
-  y2 = hovered_rect_f.y();
-
-  // Vertical left distance line.
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-}
-
-void DrawR1TopPartialLeftR2(const gfx::RectF& pinned_rect_f,
-                            const gfx::RectF& hovered_rect_f,
-                            const cc::PaintFlags& flags,
-                            gfx::Canvas* canvas,
-                            gfx::RenderText* render_text) {
-  float x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
-  float y1 = hovered_rect_f.bottom();
-  float x2 = x1;
-  float y2 = pinned_rect_f.y();
-
-  // Vertical left dotted line.
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-}
-
-void DrawR1BottomPartialLeftR2(const gfx::RectF& pinned_rect_f,
-                               const gfx::RectF& hovered_rect_f,
-                               const cc::PaintFlags& flags,
-                               gfx::Canvas* canvas,
-                               gfx::RenderText* render_text) {
-  float x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
-  float y1 = pinned_rect_f.bottom();
-  float x2 = x1;
-  float y2 = hovered_rect_f.y();
-
-  // Vertical left dotted line.
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-}
-
-void DrawR1IntersectsR2(const gfx::RectF& pinned_rect_f,
-                        const gfx::RectF& hovered_rect_f,
-                        const cc::PaintFlags& flags,
-                        gfx::Canvas* canvas,
-                        gfx::RenderText* render_text) {
-  // Vertical dotted line for the top side of the pinned rectangle
-  float x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
-  float y1 = pinned_rect_f.y();
-  float x2 = x1;
-  float y2 = hovered_rect_f.y();
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-
-  // Vertical dotted line for the bottom side of the pinned rectangle
-  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
-  y1 = pinned_rect_f.bottom();
-  x2 = x1;
-  y2 = hovered_rect_f.bottom();
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
-                        render_text);
-
-  // Horizontal dotted line for the left side of the pinned rectangle
-  x1 = pinned_rect_f.x();
-  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
-  x2 = hovered_rect_f.x();
-  y2 = y1;
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-
-  // Horizontal dotted line for the right side of the pinned rectangle
-  x1 = pinned_rect_f.right();
-  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
-  x2 = hovered_rect_f.right();
-  y2 = y1;
-  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
-  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
-                        render_text);
-}
-
-}  // namespace
-
 OverlayAgentAura* OverlayAgentAura::overlay_agent_aura_ = nullptr;
 
-OverlayAgentAura::OverlayAgentAura(DOMAgentAura* dom_agent, aura::Env* env)
-    : OverlayAgent(dom_agent),
-      show_size_on_canvas_(false),
-      highlight_rect_config_(HighlightRectsConfiguration::NO_DRAW) {
+OverlayAgentAura::OverlayAgentAura(DOMAgent* dom_agent)
+    : OverlayAgentViews(dom_agent) {
   DCHECK(!overlay_agent_aura_);
+  RegisterEnv(aura::Env::GetInstance());
   overlay_agent_aura_ = this;
-  RegisterEnv(env);
 }
 
 OverlayAgentAura::~OverlayAgentAura() {
@@ -389,36 +29,14 @@
   envs_.push_back(env);
 }
 
-void OverlayAgentAura::SetPinnedNodeId(int node_id) {
-  pinned_id_ = node_id;
-  frontend()->nodeHighlightRequested(pinned_id_);
-  HighlightNode(pinned_id_, true /* show_size */);
+void OverlayAgentAura::InstallPreTargetHandler() {
+  for (auto* env : envs_)
+    env->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
 }
 
-protocol::Response OverlayAgentAura::setInspectMode(
-    const protocol::String& in_mode,
-    protocol::Maybe<protocol::Overlay::HighlightConfig> in_highlightConfig) {
-  pinned_id_ = 0;
-  if (in_mode.compare("searchForNode") == 0) {
-    for (auto* env : envs_)
-      env->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
-  } else if (in_mode.compare("none") == 0) {
-    for (auto* env : envs_)
-      env->RemovePreTargetHandler(this);
-  }
-  return protocol::Response::OK();
-}
-
-protocol::Response OverlayAgentAura::highlightNode(
-    std::unique_ptr<protocol::Overlay::HighlightConfig> highlight_config,
-    protocol::Maybe<int> node_id) {
-  return HighlightNode(node_id.fromJust());
-}
-
-protocol::Response OverlayAgentAura::hideHighlight() {
-  if (layer_for_highlighting_ && layer_for_highlighting_->visible())
-    layer_for_highlighting_->SetVisible(false);
-  return Response::OK();
+void OverlayAgentAura::RemovePreTargetHandler() {
+  for (auto* env : envs_)
+    env->RemovePreTargetHandler(this);
 }
 
 int OverlayAgentAura::FindElementIdTargetedByPoint(
@@ -433,23 +51,17 @@
   views::Widget* targeted_widget =
       views::Widget::GetWidgetForNativeWindow(targeted_window);
   if (!targeted_widget) {
-#if defined(USE_AURA)
     return dom_agent()
         ->element_root()
         ->FindUIElementIdForBackendElement<aura::Window>(targeted_window);
-#else
-    return 0;
-#endif  // defined(USE_AURA)
   }
 
   views::View* root_view = targeted_widget->GetRootView();
   DCHECK(root_view);
 
   gfx::Point point_in_targeted_window(p);
-#if defined(USE_AURA)
   aura::Window::ConvertPointToTarget(root_window, targeted_window,
                                      &point_in_targeted_window);
-#endif  // defined(USE_AURA)
   views::View* targeted_view =
       root_view->GetEventHandlerForPoint(point_in_targeted_window);
   DCHECK(targeted_view);
@@ -458,321 +70,10 @@
       ->FindUIElementIdForBackendElement<views::View>(targeted_view);
 }
 
-void OverlayAgentAura::ShowDistancesInHighlightOverlay(int pinned_id,
-                                                       int element_id) {
-  const std::pair<gfx::NativeWindow, gfx::Rect> pair_r2(
-      dom_agent()
-          ->GetElementFromNodeId(element_id)
-          ->GetNodeWindowAndScreenBounds());
-  const std::pair<gfx::NativeWindow, gfx::Rect> pair_r1(
-      dom_agent()
-          ->GetElementFromNodeId(pinned_id)
-          ->GetNodeWindowAndScreenBounds());
-  gfx::Rect r2(pair_r2.second);
-  gfx::Rect r1(pair_r1.second);
-  pinned_rect_ = r1;
-
-  is_swap_ = false;
-  if (r1.x() > r2.x()) {
-    is_swap_ = true;
-    std::swap(r1, r2);
-  }
-  if (r1.Contains(r2)) {
-    highlight_rect_config_ = HighlightRectsConfiguration::R1_CONTAINS_R2;
-  } else if (r1.right() <= r2.x()) {
-    if ((r1.y() <= r2.y() && r2.y() <= r1.bottom()) ||
-        (r1.y() <= r2.bottom() && r2.bottom() <= r1.bottom()) ||
-        (r2.y() <= r1.y() && r1.y() <= r2.bottom()) ||
-        (r2.y() <= r1.bottom() && r1.bottom() <= r2.bottom())) {
-      highlight_rect_config_ =
-          HighlightRectsConfiguration::R1_HORIZONTAL_FULL_LEFT_R2;
-    } else if (r1.bottom() <= r2.y()) {
-      highlight_rect_config_ = HighlightRectsConfiguration::R1_TOP_FULL_LEFT_R2;
-    } else if (r1.y() >= r2.bottom()) {
-      highlight_rect_config_ =
-          HighlightRectsConfiguration::R1_BOTTOM_FULL_LEFT_R2;
-    }
-  } else if (r1.x() <= r2.x() && r2.x() <= r1.right()) {
-    if (r1.bottom() <= r2.y()) {
-      highlight_rect_config_ =
-          HighlightRectsConfiguration::R1_TOP_PARTIAL_LEFT_R2;
-    } else if (r1.y() >= r2.bottom()) {
-      highlight_rect_config_ =
-          HighlightRectsConfiguration::R1_BOTTOM_PARTIAL_LEFT_R2;
-    } else if (r1.Intersects(r2)) {
-      highlight_rect_config_ = HighlightRectsConfiguration::R1_INTERSECTS_R2;
-    } else {
-      NOTREACHED();
-    }
-  } else {
-    highlight_rect_config_ = HighlightRectsConfiguration::NO_DRAW;
-  }
-}
-
-Response OverlayAgentAura::HighlightNode(int node_id, bool show_size) {
-  UIElement* element = dom_agent()->GetElementFromNodeId(node_id);
-  if (!element)
-    return Response::Error("No node found with that id");
-
-  if (!layer_for_highlighting_) {
-    layer_for_highlighting_.reset(new ui::Layer(ui::LayerType::LAYER_TEXTURED));
-    layer_for_highlighting_->set_name("HighlightingLayer");
-    layer_for_highlighting_->set_delegate(this);
-    layer_for_highlighting_->SetFillsBoundsOpaquely(false);
-  }
-
-  highlight_rect_config_ = HighlightRectsConfiguration::NO_DRAW;
-  show_size_on_canvas_ = show_size;
-  layer_for_highlighting_->SetVisible(
-      UpdateHighlight(element->GetNodeWindowAndScreenBounds()));
-  return Response::OK();
-}
-
-bool OverlayAgentAura::UpdateHighlight(
-    const std::pair<gfx::NativeWindow, gfx::Rect>& window_and_bounds) {
-  if (window_and_bounds.second.IsEmpty()) {
-    hovered_rect_.SetRect(0, 0, 0, 0);
-    return false;
-  }
-
-  gfx::NativeWindow root = window_and_bounds.first->GetRootWindow();
-#if defined(OS_CHROMEOS)
-  // Get the screen's display-root window; otherwise, if the window belongs to
-  // a window service client, |root| will only be a client-root window.
-  aura::Window* window = display::Screen::GetScreen()->GetWindowAtScreenPoint(
-      root->GetBoundsInScreen().origin());
-  if (window)  // May be null in unit tests.
-    root = window->GetRootWindow();
-#endif  // OS_CHROMEOS
-
-  layer_for_highlighting_->SetBounds(root->bounds());
-  layer_for_highlighting_->SchedulePaint(root->bounds());
-  layer_for_highlighting_screen_offset_ =
-      root->GetBoundsInScreen().OffsetFromOrigin();
-
-  if (root->layer() != layer_for_highlighting_->parent())
-    root->layer()->Add(layer_for_highlighting_.get());
-  else
-    root->layer()->StackAtTop(layer_for_highlighting_.get());
-
-  hovered_rect_ = window_and_bounds.second;
-  return true;
-}
-
-void OverlayAgentAura::OnMouseEvent(ui::MouseEvent* event) {
-  // Make sure the element tree has been populated before processing
-  // mouse events.
-  if (!dom_agent()->element_root())
-    return;
-
-  // Show parent of the pinned element with id |pinned_id_| when mouse scrolls
-  // up. If parent exists, hightlight and re-pin parent element.
-  if (event->type() == ui::ET_MOUSEWHEEL && pinned_id_) {
-    const ui::MouseWheelEvent* mouse_event =
-        static_cast<ui::MouseWheelEvent*>(event);
-    DCHECK(mouse_event);
-    if (mouse_event->y_offset() > 0) {
-      const int parent_node_id = dom_agent()->GetParentIdOfNodeId(pinned_id_);
-      if (parent_node_id)
-        SetPinnedNodeId(parent_node_id);
-    } else if (mouse_event->y_offset() < 0) {
-      // TODO(thanhph): discuss behaviours when mouse scrolls down.
-    }
-    return;
-  }
-
-  // Find node id of element whose bounds contain the mouse pointer location.
-  int element_id = FindElementIdTargetedByPoint(event);
-  if (!element_id)
-    return;
-
-#if defined(USE_AURA)
-  aura::Window* target = static_cast<aura::Window*>(event->target());
-  bool active_window = ::wm::IsActiveWindow(
-      target->GetRootWindow()->GetEventHandlerForPoint(event->root_location()));
-#else
-  bool active_window = true;
-#endif
-  if (pinned_id_ == element_id && active_window) {
-    event->SetHandled();
-    return;
-  }
-
-  // Pin the hover element on click.
-  if (event->type() == ui::ET_MOUSE_PRESSED) {
-    if (active_window)
-      event->SetHandled();
-    SetPinnedNodeId(element_id);
-  } else if (pinned_id_) {
-    // If hovering with a pinned element, then show distances between the pinned
-    // element and the hover element.
-    HighlightNode(element_id, false /* show_size */);
-    ShowDistancesInHighlightOverlay(pinned_id_, element_id);
-  } else {
-    // Display only guidelines if hovering without a pinned element.
-    frontend()->nodeHighlightRequested(element_id);
-    HighlightNode(element_id, false /* show_size */);
-  }
-}
-
-void OverlayAgentAura::OnKeyEvent(ui::KeyEvent* event) {
-  if (!dom_agent()->element_root())
-    return;
-
-  // Exit inspect mode by pressing ESC key.
-  if (event->key_code() == ui::KeyboardCode::VKEY_ESCAPE) {
-    for (auto* env : envs_)
-      env->RemovePreTargetHandler(this);
-    if (pinned_id_) {
-      frontend()->inspectNodeRequested(pinned_id_);
-      HighlightNode(pinned_id_, true /* show_size */);
-    }
-    // Unpin element.
-    pinned_id_ = 0;
-  }
-}
-
-void OverlayAgentAura::OnPaintLayer(const ui::PaintContext& context) {
-  const gfx::Rect& screen_bounds(layer_for_highlighting_->bounds());
-  ui::PaintRecorder recorder(context, screen_bounds.size());
-  gfx::Canvas* canvas = recorder.canvas();
-  // Convert the hovered rect from screen coordinates to layer coordinates.
-  gfx::RectF hovered_rect_f(hovered_rect_);
-  hovered_rect_f.Offset(-layer_for_highlighting_screen_offset_);
-
-  cc::PaintFlags flags;
-  flags.setStrokeWidth(1.0f);
-  flags.setColor(SK_ColorBLUE);
-  flags.setStyle(cc::PaintFlags::kStroke_Style);
-
-  constexpr SkScalar intervals[] = {1.f, 4.f};
-  flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
-  if (!render_text_)
-    render_text_ = gfx::RenderText::CreateHarfBuzzInstance();
-  DrawRulers(screen_bounds, canvas, render_text_.get());
-
-  // Display guide lines if |highlight_rect_config_| is NO_DRAW.
-  if (highlight_rect_config_ == HighlightRectsConfiguration::NO_DRAW) {
-    hovered_rect_f.Inset(gfx::InsetsF(-1));
-    DrawRectGuideLinesOnCanvas(screen_bounds, hovered_rect_f, flags, canvas);
-    // Draw |hovered_rect_f| bounds.
-    flags.setPathEffect(nullptr);
-    canvas->DrawRect(hovered_rect_f, flags);
-
-    // Display size of the rectangle after mouse click.
-    if (show_size_on_canvas_) {
-      DrawSizeOfRectangle(gfx::ToNearestRect(hovered_rect_f),
-                          RectSide::BOTTOM_SIDE, canvas, render_text_.get());
-    }
-    return;
-  }
-  flags.setPathEffect(nullptr);
-  flags.setColor(SK_ColorBLUE);
-
-  // Convert the pinned rect from screen coordinates to layer coordinates.
-  gfx::RectF pinned_rect_f(pinned_rect_);
-  pinned_rect_f.Offset(-layer_for_highlighting_screen_offset_);
-
-  // Draw |pinned_rect_f| bounds in blue.
-  canvas->DrawRect(pinned_rect_f, flags);
-
-  // Draw |hovered_rect_f| bounds in green.
-  flags.setColor(SK_ColorGREEN);
-  canvas->DrawRect(hovered_rect_f, flags);
-
-  // Draw distances in red colour.
-  flags.setPathEffect(nullptr);
-  flags.setColor(SK_ColorRED);
-
-  // Make sure |pinned_rect_f| stays on the right or below of |hovered_rect_f|.
-  if (pinned_rect_f.x() < hovered_rect_f.x() ||
-      (pinned_rect_f.x() == hovered_rect_f.x() &&
-       pinned_rect_f.y() < hovered_rect_f.y())) {
-    std::swap(pinned_rect_f, hovered_rect_f);
-  }
-
-  switch (highlight_rect_config_) {
-    case HighlightRectsConfiguration::R1_CONTAINS_R2:
-      DrawR1ContainsR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                       render_text_.get());
-      return;
-    case HighlightRectsConfiguration::R1_HORIZONTAL_FULL_LEFT_R2:
-      DrawR1HorizontalFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                                 render_text_.get());
-      return;
-    case HighlightRectsConfiguration::R1_TOP_FULL_LEFT_R2:
-      DrawR1TopFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                          render_text_.get());
-
-      // Draw 4 guide lines along distance lines.
-      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
-      // Bottom horizontal dotted line from left to right.
-      canvas->DrawLine(
-          gfx::PointF(0.0f, hovered_rect_f.bottom()),
-          gfx::PointF(screen_bounds.right(), hovered_rect_f.bottom()), flags);
-
-      // Right vertical dotted line from top to bottom.
-      canvas->DrawLine(
-          gfx::PointF(hovered_rect_f.right(), 0.0f),
-          gfx::PointF(hovered_rect_f.right(), screen_bounds.bottom()), flags);
-
-      // Top horizontal dotted line from left to right.
-      canvas->DrawLine(gfx::PointF(0.0f, pinned_rect_f.y()),
-                       gfx::PointF(screen_bounds.right(), pinned_rect_f.y()),
-                       flags);
-
-      // Left vertical dotted line from top to bottom.
-      canvas->DrawLine(gfx::PointF(pinned_rect_f.x(), 0.0f),
-                       gfx::PointF(pinned_rect_f.x(), screen_bounds.bottom()),
-                       flags);
-      return;
-    case HighlightRectsConfiguration::R1_BOTTOM_FULL_LEFT_R2:
-      DrawR1BottomFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                             render_text_.get());
-
-      // Draw 2 guide lines along distance lines.
-      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
-      // Top horizontal dotted line from left to right.
-      canvas->DrawLine(
-          gfx::PointF(0.0f, pinned_rect_f.bottom()),
-          gfx::PointF(screen_bounds.right(), pinned_rect_f.bottom()), flags);
-
-      // Left vertical dotted line from top to bottom.
-      canvas->DrawLine(gfx::PointF(pinned_rect_f.x(), 0.0f),
-                       gfx::PointF(pinned_rect_f.x(), screen_bounds.bottom()),
-                       flags);
-      return;
-    case HighlightRectsConfiguration::R1_TOP_PARTIAL_LEFT_R2:
-      DrawR1TopPartialLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                             render_text_.get());
-
-      // Draw 1 guide line along distance lines.
-      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
-      // Top horizontal dotted line from left to right.
-      canvas->DrawLine(gfx::PointF(0.0f, pinned_rect_f.y()),
-                       gfx::PointF(screen_bounds.right(), pinned_rect_f.y()),
-                       flags);
-      return;
-    case HighlightRectsConfiguration::R1_BOTTOM_PARTIAL_LEFT_R2:
-      DrawR1BottomPartialLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                                render_text_.get());
-      return;
-    case HighlightRectsConfiguration::R1_INTERSECTS_R2:
-      DrawR1IntersectsR2(pinned_rect_f, hovered_rect_f, flags, canvas,
-                         render_text_.get());
-      // Draw 4 guide line along distance lines.
-      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
-      DrawRectGuideLinesOnCanvas(screen_bounds, hovered_rect_f, flags, canvas);
-      return;
-    default:
-      NOTREACHED();
-      return;
-  }
+// static
+std::unique_ptr<OverlayAgentViews> OverlayAgentViews::Create(
+    DOMAgent* dom_agent) {
+  return std::make_unique<OverlayAgentAura>(dom_agent);
 }
 
 }  // namespace ui_devtools
diff --git a/components/ui_devtools/views/overlay_agent_aura.h b/components/ui_devtools/views/overlay_agent_aura.h
index b371cba4..1af54dd 100644
--- a/components/ui_devtools/views/overlay_agent_aura.h
+++ b/components/ui_devtools/views/overlay_agent_aura.h
@@ -5,120 +5,35 @@
 #ifndef COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_AURA_H_
 #define COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_AURA_H_
 
-#include <vector>
+#include "components/ui_devtools/views/overlay_agent_views.h"
 
-#include "components/ui_devtools/Overlay.h"
-#include "components/ui_devtools/overlay_agent.h"
-#include "components/ui_devtools/views/dom_agent_aura.h"
-#include "ui/compositor/layer_delegate.h"
-#include "ui/events/event_handler.h"
-#include "ui/gfx/native_widget_types.h"
+#include "components/ui_devtools/dom_agent.h"
 
 namespace aura {
 class Env;
 }
 
-namespace gfx {
-class RenderText;
-}
-
 namespace ui_devtools {
 
-enum HighlightRectsConfiguration {
-  NO_DRAW,
-  R1_CONTAINS_R2,
-  R1_HORIZONTAL_FULL_LEFT_R2,
-  R1_TOP_FULL_LEFT_R2,
-  R1_BOTTOM_FULL_LEFT_R2,
-  R1_TOP_PARTIAL_LEFT_R2,
-  R1_BOTTOM_PARTIAL_LEFT_R2,
-  R1_INTERSECTS_R2
-};
-
-enum RectSide { TOP_SIDE, LEFT_SIDE, RIGHT_SIDE, BOTTOM_SIDE };
-
-class OverlayAgentAura : public OverlayAgent,
-                         public ui::EventHandler,
-                         public ui::LayerDelegate {
+class OverlayAgentAura : public OverlayAgentViews {
  public:
-  OverlayAgentAura(DOMAgentAura* dom_agent, aura::Env* env);
+  OverlayAgentAura(DOMAgent* dom_agent);
   ~OverlayAgentAura() override;
-
-  static OverlayAgentAura* GetInstance() { return overlay_agent_aura_; }
-
   void RegisterEnv(aura::Env* env);
 
-  int pinned_id() const { return pinned_id_; }
-  void SetPinnedNodeId(int pinned_id);
-
-  // Overlay::Backend:
-  protocol::Response setInspectMode(
-      const protocol::String& in_mode,
-      protocol::Maybe<protocol::Overlay::HighlightConfig> in_highlightConfig)
-      override;
-  protocol::Response highlightNode(
-      std::unique_ptr<protocol::Overlay::HighlightConfig> highlight_config,
-      protocol::Maybe<int> node_id) override;
-  protocol::Response hideHighlight() override;
-
-  HighlightRectsConfiguration highlight_rect_config() const {
-    return highlight_rect_config_;
-  }
-
-  // Return the id of the UI element located at |event|'s root location.
-  // The function first searches for the targeted window, then the targeted
-  // widget (if one exists), then the targeted view (if one exists). Return 0 if
-  // no valid target is found.
-  int FindElementIdTargetedByPoint(ui::LocatedEvent* event) const;
+  int FindElementIdTargetedByPoint(ui::LocatedEvent* event) const override;
+  static OverlayAgentAura* GetInstance() { return overlay_agent_aura_; }
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest,
-                           MouseEventsGenerateFEEventsInInspectMode);
-  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightRects);
-  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightNonexistentNode);
-  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightWidget);
-#if defined(USE_AURA)
+  void InstallPreTargetHandler() override;
+  void RemovePreTargetHandler() override;
+
   FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightWindow);
   FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightEmptyOrInvisibleWindow);
-#endif
-  protocol::Response HighlightNode(int node_id, bool show_size = false);
-  // Returns true when there is any visible element to highlight.
-  bool UpdateHighlight(
-      const std::pair<gfx::NativeWindow, gfx::Rect>& window_and_screen_bounds);
-
-  // Shows the distances between the nodes identified by |pinned_id| and
-  // |element_id| in the highlight overlay.
-  void ShowDistancesInHighlightOverlay(int pinned_id, int element_id);
-
-  // ui:EventHandler:
-  void OnMouseEvent(ui::MouseEvent* event) override;
-  void OnKeyEvent(ui::KeyEvent* event) override;
-
-  // ui::LayerDelegate:
-  void OnPaintLayer(const ui::PaintContext& context) override;
-  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
-                                  float new_device_scale_factor) override {}
-
-  ui::Layer* layer_for_highlighting() { return layer_for_highlighting_.get(); }
+  std::vector<aura::Env*> envs_;
 
   static OverlayAgentAura* overlay_agent_aura_;
 
-  std::vector<aura::Env*> envs_;
-  std::unique_ptr<gfx::RenderText> render_text_;
-  bool show_size_on_canvas_ = false;
-  HighlightRectsConfiguration highlight_rect_config_;
-  bool is_swap_ = false;
-
-  // The layer used to paint highlights, and its offset from the screen origin.
-  std::unique_ptr<ui::Layer> layer_for_highlighting_;
-  gfx::Vector2d layer_for_highlighting_screen_offset_;
-
-  // Hovered and pinned element bounds in screen coordinates; empty if none.
-  gfx::Rect hovered_rect_;
-  gfx::Rect pinned_rect_;
-
-  int pinned_id_ = 0;
-
   DISALLOW_COPY_AND_ASSIGN(OverlayAgentAura);
 };
 
diff --git a/components/ui_devtools/views/overlay_agent_mac.h b/components/ui_devtools/views/overlay_agent_mac.h
new file mode 100644
index 0000000..105e3369
--- /dev/null
+++ b/components/ui_devtools/views/overlay_agent_mac.h
@@ -0,0 +1,48 @@
+// Copyright 2019 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 COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_MAC_H_
+#define COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_MAC_H_
+
+#include "components/ui_devtools/views/overlay_agent_views.h"
+
+#include "components/ui_devtools/dom_agent.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace ui_devtools {
+
+class OverlayAgentMac : public OverlayAgentViews,
+                        public DOMAgentObserver,
+                        public views::WidgetObserver {
+ public:
+  OverlayAgentMac(DOMAgent* dom_agent);
+  ~OverlayAgentMac() override;
+  int FindElementIdTargetedByPoint(ui::LocatedEvent* event) const override;
+
+  // DevTools protocol generated backend classes.
+  protocol::Response enable() override;
+  protocol::Response disable() override;
+
+  // DOMAgentObserver
+  void OnElementAdded(UIElement* ui_element) override;
+
+  // views::WidgetObserver
+  void OnWidgetDestroying(views::Widget* widget) override;
+
+ private:
+  // OverlayAgentViews
+  void InstallPreTargetHandler() override;
+  void RemovePreTargetHandler() override;
+
+  void InstallPreTargetHandlerOnWidget(views::Widget* widget);
+  void RemovePreTargetHandlerOnWidget(views::Widget* widget);
+
+  bool is_pretarget_handler_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(OverlayAgentMac);
+};
+
+}  // namespace ui_devtools
+
+#endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_MAC_H_
diff --git a/components/ui_devtools/views/overlay_agent_mac.mm b/components/ui_devtools/views/overlay_agent_mac.mm
new file mode 100644
index 0000000..61307f0
--- /dev/null
+++ b/components/ui_devtools/views/overlay_agent_mac.mm
@@ -0,0 +1,96 @@
+// Copyright 2019 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 "components/ui_devtools/views/overlay_agent_mac.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "components/ui_devtools/views/view_element.h"
+#include "components/ui_devtools/views/widget_element.h"
+
+namespace ui_devtools {
+
+OverlayAgentMac::OverlayAgentMac(DOMAgent* dom_agent)
+    : OverlayAgentViews(dom_agent) {}
+
+OverlayAgentMac::~OverlayAgentMac() {
+  if (is_pretarget_handler_)
+    RemovePreTargetHandler();
+}
+
+void OverlayAgentMac::InstallPreTargetHandler() {
+  DCHECK(!is_pretarget_handler_);
+  is_pretarget_handler_ = true;
+  for (NSWindow* window in [NSApp windows]) {
+    InstallPreTargetHandlerOnWidget(
+        views::Widget::GetWidgetForNativeWindow(window));
+  }
+}
+
+void OverlayAgentMac::RemovePreTargetHandler() {
+  DCHECK(is_pretarget_handler_);
+  is_pretarget_handler_ = false;
+  for (NSWindow* window in [NSApp windows]) {
+    views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+    RemovePreTargetHandlerOnWidget(widget);
+  }
+}
+
+void OverlayAgentMac::OnElementAdded(UIElement* element) {
+  if (!is_pretarget_handler_ || element->type() != UIElementType::WIDGET)
+    return;
+  views::Widget* widget =
+      UIElement::GetBackingElement<views::Widget, WidgetElement>(element);
+  InstallPreTargetHandlerOnWidget(widget);
+}
+
+void OverlayAgentMac::OnWidgetDestroying(views::Widget* widget) {
+  if (!is_pretarget_handler_)
+    return;
+  RemovePreTargetHandlerOnWidget(widget);
+}
+
+int OverlayAgentMac::FindElementIdTargetedByPoint(
+    ui::LocatedEvent* event) const {
+  views::View* target = static_cast<views::View*>(event->target());
+  gfx::Point p = event->root_location();
+  views::View* targeted_view = target->GetEventHandlerForPoint(p);
+  DCHECK(targeted_view);
+  return dom_agent()
+      ->element_root()
+      ->FindUIElementIdForBackendElement<views::View>(targeted_view);
+}
+
+protocol::Response OverlayAgentMac::enable() {
+  dom_agent()->AddObserver(this);
+  return OverlayAgentViews::enable();
+}
+
+protocol::Response OverlayAgentMac::disable() {
+  if (is_pretarget_handler_)
+    RemovePreTargetHandler();
+  dom_agent()->RemoveObserver(this);
+  return OverlayAgentViews::disable();
+}
+void OverlayAgentMac::InstallPreTargetHandlerOnWidget(views::Widget* widget) {
+  if (!widget)
+    return;
+  widget->GetRootView()->AddPreTargetHandler(
+      this, ui::EventTarget::Priority::kSystem);
+  widget->AddObserver(this);
+}
+void OverlayAgentMac::RemovePreTargetHandlerOnWidget(views::Widget* widget) {
+  if (!widget)
+    return;
+  widget->GetRootView()->RemovePreTargetHandler(this);
+  widget->RemoveObserver(this);
+}
+
+// static
+std::unique_ptr<OverlayAgentViews> OverlayAgentViews::Create(
+    DOMAgent* dom_agent) {
+  return std::make_unique<OverlayAgentMac>(dom_agent);
+}
+
+}  // namespace ui_devtools
diff --git a/components/ui_devtools/views/overlay_agent_unittest.cc b/components/ui_devtools/views/overlay_agent_unittest.cc
index 8223fce..e3d1e38 100644
--- a/components/ui_devtools/views/overlay_agent_unittest.cc
+++ b/components/ui_devtools/views/overlay_agent_unittest.cc
@@ -2,15 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/ui_devtools/views/overlay_agent_aura.h"
+#include "components/ui_devtools/views/overlay_agent_views.h"
 
 #include "components/ui_devtools/ui_devtools_unittest_utils.h"
 #include "components/ui_devtools/ui_element.h"
-#include "components/ui_devtools/views/dom_agent_aura.h"
-#include "components/ui_devtools/views/overlay_agent_aura.h"
+#include "components/ui_devtools/views/dom_agent_views.h"
 #include "components/ui_devtools/views/view_element.h"
 #include "components/ui_devtools/views/widget_element.h"
-#include "components/ui_devtools/views/window_element.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/test/event_generator.h"
@@ -20,6 +18,7 @@
 #include "ui/views/window/non_client_view.h"
 
 #if defined(USE_AURA)
+#include "components/ui_devtools/views/window_element.h"
 #include "ui/aura/env.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
@@ -28,7 +27,6 @@
 namespace ui_devtools {
 
 namespace {
-const SkColor kBackgroundColor = 0;
 
 gfx::Point GetOriginInScreen(views::View* view) {
   gfx::Point point(0, 0);  // Since it's local bounds, origin is always 0,0.
@@ -44,10 +42,9 @@
     fake_frontend_channel_ = std::make_unique<FakeFrontendChannel>();
     uber_dispatcher_ = std::make_unique<protocol::UberDispatcher>(
         fake_frontend_channel_.get());
-    aura::Env* env = aura::Env::GetInstance();
-    dom_agent_ = std::make_unique<DOMAgentAura>(env);
+    dom_agent_ = DOMAgentViews::Create();
     dom_agent_->Init(uber_dispatcher_.get());
-    overlay_agent_ = std::make_unique<OverlayAgentAura>(dom_agent_.get(), env);
+    overlay_agent_ = OverlayAgentViews::Create(dom_agent_.get());
     overlay_agent_->Init(uber_dispatcher_.get());
     overlay_agent_->enable();
     views::ViewsTestBase::SetUp();
@@ -60,17 +57,24 @@
     dom_agent_.reset();
     uber_dispatcher_.reset();
     fake_frontend_channel_.reset();
+    widget_.reset();
     views::ViewsTestBase::TearDown();
   }
 
  protected:
   std::unique_ptr<ui::MouseEvent> MouseEventAtRootLocation(gfx::Point p) {
+#if defined(USE_AURA)
+    ui::EventTarget* target = GetContext();
+#else
+    ui::EventTarget* target = widget()->GetRootView();
+#endif
     auto event = std::make_unique<ui::MouseEvent>(ui::ET_MOUSE_MOVED, p, p,
                                                   ui::EventTimeForNow(),
                                                   ui::EF_NONE, ui::EF_NONE);
-    ui::Event::DispatcherApi(event.get()).set_target(GetContext());
+    ui::Event::DispatcherApi(event.get()).set_target(target);
     return event;
   }
+
   views::View* GetViewAtPoint(int x, int y) {
     gfx::Point point(x, y);
     int element_id = overlay_agent()->FindElementIdTargetedByPoint(
@@ -95,6 +99,7 @@
             node_id));
   }
 
+#if defined(USE_AURA)
   std::unique_ptr<aura::Window> CreateWindowElement(const gfx::Rect& bounds) {
     std::unique_ptr<aura::Window> window = std::make_unique<aura::Window>(
         nullptr, aura::client::WINDOW_TYPE_NORMAL);
@@ -104,35 +109,38 @@
     window->Show();
     return window;
   }
+#endif
 
-  std::unique_ptr<views::Widget> CreateWidget(const gfx::Rect& bounds) {
-    auto widget = std::make_unique<views::Widget>();
+  void CreateWidget(const gfx::Rect& bounds) {
+    widget_ = std::make_unique<views::Widget>();
     views::Widget::InitParams params;
     params.delegate = nullptr;
     params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
     params.bounds = bounds;
+#if defined(USE_AURA)
     params.parent = GetContext();
-    widget->Init(params);
-    widget->Show();
-    return widget;
+#endif
+    widget_->Init(params);
+    widget_->Show();
   }
 
-  std::unique_ptr<views::Widget> CreateWidget() {
+  void CreateWidget() {
     // Create a widget with default bounds.
     return CreateWidget(gfx::Rect(0, 0, 400, 400));
   }
 
-  DOMAgent* dom_agent() { return dom_agent_.get(); }
-  OverlayAgentAura* overlay_agent() { return overlay_agent_.get(); }
+  views::Widget* widget() { return widget_.get(); }
+  DOMAgentViews* dom_agent() { return dom_agent_.get(); }
+  OverlayAgentViews* overlay_agent() { return overlay_agent_.get(); }
   FakeFrontendChannel* frontend_channel() {
     return fake_frontend_channel_.get();
   }
 
- private:
   std::unique_ptr<protocol::UberDispatcher> uber_dispatcher_;
   std::unique_ptr<FakeFrontendChannel> fake_frontend_channel_;
-  std::unique_ptr<DOMAgentAura> dom_agent_;
-  std::unique_ptr<OverlayAgentAura> overlay_agent_;
+  std::unique_ptr<DOMAgentViews> dom_agent_;
+  std::unique_ptr<OverlayAgentViews> overlay_agent_;
+  std::unique_ptr<views::Widget> widget_;
 };
 
 #if defined(USE_AURA)
@@ -165,12 +173,12 @@
 #endif
 
 TEST_F(OverlayAgentTest, FindElementIdTargetedByPointViews) {
-  std::unique_ptr<views::Widget> widget = CreateWidget();
+  CreateWidget();
 
   std::unique_ptr<protocol::DOM::Node> root;
   dom_agent()->getDocument(&root);
 
-  views::View* contents_view = widget->GetContentsView();
+  views::View* contents_view = widget()->GetContentsView();
   contents_view->RemoveAllChildViews(true);
 
   views::View* child_1 = new views::View;
@@ -192,7 +200,7 @@
   child_1->SetBounds(20, 20, 100, 100);
   child_2->SetBounds(90, 50, 100, 100);
 
-  EXPECT_EQ(GetViewAtPoint(1, 1), widget->GetContentsView());
+  EXPECT_EQ(GetViewAtPoint(1, 1), widget()->GetContentsView());
   EXPECT_EQ(GetViewAtPoint(21, 21), child_1);
   EXPECT_EQ(GetViewAtPoint(170, 130), child_2);
   // At the overlap.
@@ -226,7 +234,10 @@
 
   for (const auto& test_case : kTestCases) {
     SCOPED_TRACE(testing::Message() << "Case: " << test_case.name);
-    std::unique_ptr<views::Widget> widget = CreateWidget(kWidgetBounds);
+    CreateWidget(kWidgetBounds);
+    // Can't just use kWidgetBounds because of Mac's menu bar.
+    gfx::Vector2d widget_screen_offset =
+        widget()->GetClientAreaBoundsInScreen().OffsetFromOrigin();
 
     std::unique_ptr<protocol::DOM::Node> root;
     dom_agent()->getDocument(&root);
@@ -234,7 +245,7 @@
     // Fish out the client view to serve as superview. Emptying out the content
     // view and adding the subviews directly causes NonClientView's hit test to
     // fail.
-    views::View* contents_view = widget->GetContentsView();
+    views::View* contents_view = widget()->GetContentsView();
     DCHECK_EQ(contents_view->GetClassName(),
               views::NonClientView::kViewClassName);
     views::NonClientView* non_client_view =
@@ -250,12 +261,13 @@
 
     overlay_agent()->setInspectMode(
         "searchForNode", protocol::Maybe<protocol::Overlay::HighlightConfig>());
-    ui::test::EventGenerator generator(GetRootWindow(widget.get()));
+    ui::test::EventGenerator generator(GetRootWindow(widget()));
+    generator.set_assume_window_at_origin(false);
 
     // Highlight child 1.
     generator.MoveMouseTo(GetOriginInScreen(child_1));
     // Click to pin it.
-    generator.PressLeftButton();
+    generator.ClickLeftButton();
     // Highlight child 2. Now, the distance overlay is showing.
     generator.MoveMouseTo(GetOriginInScreen(child_2));
 
@@ -265,11 +277,11 @@
     // Check results of pinned and hovered rectangles.
     gfx::Rect expected_pinned_rect =
         client_view->ConvertRectToParent(test_case.first_element_bounds);
-    expected_pinned_rect.Offset(kWidgetBounds.OffsetFromOrigin());
+    expected_pinned_rect.Offset(widget_screen_offset);
     EXPECT_EQ(expected_pinned_rect, overlay_agent()->pinned_rect_);
     gfx::Rect expected_hovered_rect =
         client_view->ConvertRectToParent(test_case.second_element_bounds);
-    expected_hovered_rect.Offset(kWidgetBounds.OffsetFromOrigin());
+    expected_hovered_rect.Offset(widget_screen_offset);
     EXPECT_EQ(expected_hovered_rect, overlay_agent()->hovered_rect_);
     // If we don't explicitly stop inspecting, we'll leave ourselves as
     // a pretarget handler for the root window and UAF in the next test.
@@ -282,7 +294,7 @@
 // Tests that the correct Overlay events are dispatched to the frontend when
 // hovering and clicking over a UI element in inspect mode.
 TEST_F(OverlayAgentTest, MouseEventsGenerateFEEventsInInspectMode) {
-  std::unique_ptr<views::Widget> widget = CreateWidget();
+  CreateWidget();
 
   std::unique_ptr<protocol::DOM::Node> root;
   dom_agent()->getDocument(&root);
@@ -298,15 +310,17 @@
 
   // Moving the mouse cursor over the widget bounds should request a node
   // highlight.
-  ui::test::EventGenerator generator(GetRootWindow(widget.get()));
+  ui::test::EventGenerator generator(GetRootWindow(widget()));
   generator.MoveMouseBy(p.x(), p.y());
 
-  // 2 mouse events ET_MOUSE_ENTERED and ET_MOUSE_MOVED are generated.
-  EXPECT_EQ(2, GetOverlayNodeHighlightRequestedCount(node_id));
+  // Aura platforms generate both ET_MOUSE_ENTERED and ET_MOUSE_MOVED for
+  // this but Mac just generates ET_MOUSE_ENTERED, so just ensure we sent
+  // at least one.
+  EXPECT_GT(GetOverlayNodeHighlightRequestedCount(node_id), 0);
   EXPECT_EQ(0, GetOverlayInspectNodeRequestedCount(node_id));
 
   // Clicking on the widget should pin that element.
-  generator.PressLeftButton();
+  generator.ClickLeftButton();
 
   // Pin parent node after mouse wheel moves up.
   int parent_id = dom_agent()->GetParentIdOfNodeId(node_id);
@@ -320,15 +334,21 @@
   int inspect_node_notification_count =
       GetOverlayInspectNodeRequestedCount(node_id);
 
-  // Press escape to exit inspect mode.
+  // Press escape to exit inspect mode. We're intentionally not supporting
+  // this on Mac due do difficulties in receiving key events without aura::Env.
+#if defined(USE_AURA)
   generator.PressKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EventFlags::EF_NONE);
-
   // Upon exiting inspect mode, the element is inspected and highlighted.
   EXPECT_EQ(inspect_node_notification_count + 1,
             GetOverlayInspectNodeRequestedCount(node_id));
   ui::Layer* highlighting_layer = overlay_agent()->layer_for_highlighting();
+  const SkColor kBackgroundColor = 0;
   EXPECT_EQ(kBackgroundColor, highlighting_layer->GetTargetColor());
   EXPECT_TRUE(highlighting_layer->visible());
+#else
+  overlay_agent()->setInspectMode(
+      "none", protocol::Maybe<protocol::Overlay::HighlightConfig>());
+#endif
 
   int highlight_notification_count =
       GetOverlayNodeHighlightRequestedCount(node_id);
@@ -416,7 +436,7 @@
 #endif
 
 TEST_F(OverlayAgentTest, HighlightWidget) {
-  std::unique_ptr<views::Widget> widget = CreateWidget();
+  CreateWidget();
 
   std::unique_ptr<protocol::DOM::Node> root;
   dom_agent()->getDocument(&root);
@@ -424,7 +444,7 @@
   int widget_id =
       dom_agent()
           ->element_root()
-          ->FindUIElementIdForBackendElement<views::Widget>(widget.get());
+          ->FindUIElementIdForBackendElement<views::Widget>(widget());
   DCHECK_NE(widget_id, 0);
 
   overlay_agent()->highlightNode(nullptr, widget_id);
diff --git a/components/ui_devtools/views/overlay_agent_views.cc b/components/ui_devtools/views/overlay_agent_views.cc
new file mode 100644
index 0000000..d2f524e
--- /dev/null
+++ b/components/ui_devtools/views/overlay_agent_views.cc
@@ -0,0 +1,747 @@
+// Copyright 2018 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 "components/ui_devtools/views/overlay_agent_views.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/ui_devtools/ui_element.h"
+#include "components/ui_devtools/views/view_element.h"
+#include "components/ui_devtools/views/widget_element.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/effects/SkDashPathEffect.h"
+#include "ui/compositor/paint_recorder.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/events/event.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/render_text.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/window.h"
+#include "ui/wm/core/window_util.h"
+#endif
+
+namespace ui_devtools {
+
+namespace {
+
+using namespace ui_devtools::protocol;
+
+void DrawRulerText(const base::string16& utf16_text,
+                   const gfx::Point& p,
+                   gfx::Canvas* canvas,
+                   gfx::RenderText* render_text_) {
+  render_text_->SetText(utf16_text);
+  render_text_->SetColor(SK_ColorRED);
+  const gfx::Rect text_rect(gfx::Rect(p, render_text_->GetStringSize()));
+  canvas->FillRect(text_rect, SK_ColorWHITE, SkBlendMode::kColor);
+  render_text_->SetDisplayRect(text_rect);
+  render_text_->Draw(canvas);
+}
+
+void DrawRulers(const gfx::Rect& screen_bounds,
+                gfx::Canvas* canvas,
+                gfx::RenderText* render_text_) {
+  // Top horizontal ruler from left to right.
+  canvas->Draw1pxLine(gfx::PointF(0.0f, 0.0f),
+                      gfx::PointF(screen_bounds.right(), 0.0f),
+                      SK_ColorMAGENTA);
+
+  // Left vertical ruler from top to bottom.
+  canvas->Draw1pxLine(gfx::PointF(0.0f, 0.0f),
+                      gfx::PointF(0.0f, screen_bounds.bottom()),
+                      SK_ColorMAGENTA);
+
+  int short_stroke = 5;
+  int long_stroke = 10;
+  int gap_between_strokes = 4;
+  int gap_between_long_stroke = 100;
+
+  // Draw top horizontal ruler.
+  for (int x = gap_between_strokes; x < screen_bounds.right();
+       x += gap_between_strokes) {
+    if (x % gap_between_long_stroke == 0) {
+      canvas->Draw1pxLine(gfx::PointF(x, 0.0f), gfx::PointF(x, long_stroke),
+                          SK_ColorMAGENTA);
+      // Draw ruler marks.
+      base::string16 utf16_text = base::UTF8ToUTF16(std::to_string(x));
+      DrawRulerText(utf16_text, gfx::Point(x + 2, long_stroke), canvas,
+                    render_text_);
+
+    } else {
+      canvas->Draw1pxLine(gfx::PointF(x, 0.0f), gfx::PointF(x, short_stroke),
+                          SK_ColorMAGENTA);
+    }
+  }
+
+  // Draw left vertical ruler.
+  for (int y = 0; y < screen_bounds.bottom(); y += gap_between_strokes) {
+    if (y % gap_between_long_stroke == 0) {
+      canvas->Draw1pxLine(gfx::PointF(0.0f, y), gfx::PointF(long_stroke, y),
+                          SK_ColorMAGENTA);
+      // Draw ruler marks.
+      base::string16 utf16_text = base::UTF8ToUTF16(std::to_string(y));
+      DrawRulerText(utf16_text, gfx::Point(short_stroke + 1, y + 2), canvas,
+                    render_text_);
+    } else {
+      canvas->Draw1pxLine(gfx::PointF(0.0f, y), gfx::PointF(short_stroke, y),
+                          SK_ColorMAGENTA);
+    }
+  }
+}
+
+// Draw width() x height() of a rectangle if not empty. Otherwise, draw either
+// width() or height() if any of them is not empty.
+void DrawSizeOfRectangle(const gfx::Rect& hovered_rect,
+                         const RectSide drawing_side,
+                         gfx::Canvas* canvas,
+                         gfx::RenderText* render_text_) {
+  base::string16 utf16_text;
+  const std::string unit = "dp";
+
+  if (!hovered_rect.IsEmpty()) {
+    utf16_text = base::UTF8ToUTF16(hovered_rect.size().ToString() + unit);
+  } else if (hovered_rect.height()) {
+    // Draw only height() if height() is not empty.
+    utf16_text =
+        base::UTF8ToUTF16(std::to_string(hovered_rect.height()) + unit);
+  } else if (hovered_rect.width()) {
+    // Draw only width() if width() is not empty.
+    utf16_text = base::UTF8ToUTF16(std::to_string(hovered_rect.width()) + unit);
+  } else {
+    // If both width() and height() are empty, canvas won't draw size.
+    return;
+  }
+  render_text_->SetText(utf16_text);
+  render_text_->SetColor(SK_ColorRED);
+
+  const gfx::Size& text_size = render_text_->GetStringSize();
+  gfx::Rect text_rect;
+  if (drawing_side == RectSide::LEFT_SIDE) {
+    const gfx::Point text_left_side(
+        hovered_rect.x() + 1,
+        hovered_rect.height() / 2 - text_size.height() / 2 + hovered_rect.y());
+    text_rect = gfx::Rect(text_left_side,
+                          gfx::Size(text_size.width(), text_size.height()));
+  } else if (drawing_side == RectSide::RIGHT_SIDE) {
+    const gfx::Point text_right_side(
+        hovered_rect.right() - 1 - text_size.width(),
+        hovered_rect.height() / 2 - text_size.height() / 2 + hovered_rect.y());
+    text_rect = gfx::Rect(text_right_side,
+                          gfx::Size(text_size.width(), text_size.height()));
+  } else if (drawing_side == RectSide::TOP_SIDE) {
+    const gfx::Point text_top_side(
+        hovered_rect.x() + hovered_rect.width() / 2 - text_size.width() / 2,
+        hovered_rect.y() + 1);
+    text_rect = gfx::Rect(text_top_side,
+                          gfx::Size(text_size.width(), text_size.height()));
+  } else if (drawing_side == RectSide::BOTTOM_SIDE) {
+    const gfx::Point text_top_side(
+        hovered_rect.x() + hovered_rect.width() / 2 - text_size.width() / 2,
+        hovered_rect.bottom() - 1 - text_size.height());
+    text_rect = gfx::Rect(text_top_side,
+                          gfx::Size(text_size.width(), text_size.height()));
+  }
+  canvas->FillRect(text_rect, SK_ColorWHITE, SkBlendMode::kColor);
+  render_text_->SetDisplayRect(text_rect);
+  render_text_->Draw(canvas);
+}
+
+void DrawRectGuideLinesOnCanvas(const gfx::Rect& screen_bounds,
+                                const gfx::RectF& rect_f,
+                                cc::PaintFlags flags,
+                                gfx::Canvas* canvas) {
+  // Top horizontal dotted line from left to right.
+  canvas->DrawLine(gfx::PointF(0.0f, rect_f.y()),
+                   gfx::PointF(screen_bounds.right(), rect_f.y()), flags);
+
+  // Bottom horizontal dotted line from left to right.
+  canvas->DrawLine(gfx::PointF(0.0f, rect_f.bottom()),
+                   gfx::PointF(screen_bounds.right(), rect_f.bottom()), flags);
+
+  // Left vertical dotted line from top to bottom.
+  canvas->DrawLine(gfx::PointF(rect_f.x(), 0.0f),
+                   gfx::PointF(rect_f.x(), screen_bounds.bottom()), flags);
+
+  // Right vertical dotted line from top to bottom.
+  canvas->DrawLine(gfx::PointF(rect_f.right(), 0.0f),
+                   gfx::PointF(rect_f.right(), screen_bounds.bottom()), flags);
+}
+
+void DrawSizeWithAnyBounds(float x1,
+                           float y1,
+                           float x2,
+                           float y2,
+                           RectSide side,
+                           gfx::Canvas* canvas,
+                           gfx::RenderText* render_text) {
+  if (x2 > x1 || y2 > y1) {
+    DrawSizeOfRectangle(gfx::Rect(x1, y1, x2 - x1, y2 - y1), side, canvas,
+                        render_text);
+  } else {
+    DrawSizeOfRectangle(gfx::Rect(x2, y2, x1 - x2, y1 - y2), side, canvas,
+                        render_text);
+  }
+}
+
+void DrawR1ContainsR2(const gfx::RectF& pinned_rect_f,
+                      const gfx::RectF& hovered_rect_f,
+                      const cc::PaintFlags& flags,
+                      gfx::Canvas* canvas,
+                      gfx::RenderText* render_text) {
+  // Horizontal left distance line.
+  float x1 = pinned_rect_f.x();
+  float y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
+  float x2 = hovered_rect_f.x();
+  float y2 = y1;
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+
+  // Horizontal right distance line.
+  x1 = hovered_rect_f.right();
+  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
+  x2 = pinned_rect_f.right();
+  y2 = y1;
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+
+  // Vertical top distance line.
+  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
+  y1 = pinned_rect_f.y();
+  x2 = x1;
+  y2 = hovered_rect_f.y();
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+
+  // Vertical bottom distance line.
+  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
+  y1 = hovered_rect_f.bottom();
+  x2 = x1;
+  y2 = pinned_rect_f.bottom();
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+}
+
+void DrawR1HorizontalFullLeftR2(const gfx::RectF& pinned_rect_f,
+                                const gfx::RectF& hovered_rect_f,
+                                const cc::PaintFlags& flags,
+                                gfx::Canvas* canvas,
+                                gfx::RenderText* render_text) {
+  // Horizontal left distance line.
+  float x1 = hovered_rect_f.right();
+  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
+  float x2 = pinned_rect_f.x();
+  float y2 = y1;
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+}
+
+void DrawR1TopFullLeftR2(const gfx::RectF& pinned_rect_f,
+                         const gfx::RectF& hovered_rect_f,
+                         const cc::PaintFlags& flags,
+                         gfx::Canvas* canvas_,
+                         gfx::RenderText* render_text) {
+  float x1 = hovered_rect_f.x() + hovered_rect_f.width();
+  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
+  float x2 = pinned_rect_f.x();
+  float y2 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
+
+  // Horizontal left dotted line.
+  canvas_->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas_,
+                        render_text);
+  x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
+  y1 = hovered_rect_f.y() + hovered_rect_f.height();
+  x2 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
+  y2 = pinned_rect_f.y();
+
+  // Vertical left dotted line.
+  canvas_->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas_,
+                        render_text);
+}
+
+void DrawR1BottomFullLeftR2(const gfx::RectF& pinned_rect_f,
+                            const gfx::RectF& hovered_rect_f,
+                            const cc::PaintFlags& flags,
+                            gfx::Canvas* canvas,
+                            gfx::RenderText* render_text) {
+  float x1 = hovered_rect_f.right();
+  float y1 = hovered_rect_f.y() + hovered_rect_f.height() / 2;
+  float x2 = pinned_rect_f.x();
+  float y2 = y1;
+
+  // Horizontal left distance line.
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+
+  x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
+  y1 = pinned_rect_f.bottom();
+  x2 = x1;
+  y2 = hovered_rect_f.y();
+
+  // Vertical left distance line.
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+}
+
+void DrawR1TopPartialLeftR2(const gfx::RectF& pinned_rect_f,
+                            const gfx::RectF& hovered_rect_f,
+                            const cc::PaintFlags& flags,
+                            gfx::Canvas* canvas,
+                            gfx::RenderText* render_text) {
+  float x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
+  float y1 = hovered_rect_f.bottom();
+  float x2 = x1;
+  float y2 = pinned_rect_f.y();
+
+  // Vertical left dotted line.
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+}
+
+void DrawR1BottomPartialLeftR2(const gfx::RectF& pinned_rect_f,
+                               const gfx::RectF& hovered_rect_f,
+                               const cc::PaintFlags& flags,
+                               gfx::Canvas* canvas,
+                               gfx::RenderText* render_text) {
+  float x1 = hovered_rect_f.x() + hovered_rect_f.width() / 2;
+  float y1 = pinned_rect_f.bottom();
+  float x2 = x1;
+  float y2 = hovered_rect_f.y();
+
+  // Vertical left dotted line.
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+}
+
+void DrawR1IntersectsR2(const gfx::RectF& pinned_rect_f,
+                        const gfx::RectF& hovered_rect_f,
+                        const cc::PaintFlags& flags,
+                        gfx::Canvas* canvas,
+                        gfx::RenderText* render_text) {
+  // Vertical dotted line for the top side of the pinned rectangle
+  float x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
+  float y1 = pinned_rect_f.y();
+  float x2 = x1;
+  float y2 = hovered_rect_f.y();
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+
+  // Vertical dotted line for the bottom side of the pinned rectangle
+  x1 = pinned_rect_f.x() + pinned_rect_f.width() / 2;
+  y1 = pinned_rect_f.bottom();
+  x2 = x1;
+  y2 = hovered_rect_f.bottom();
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::LEFT_SIDE, canvas,
+                        render_text);
+
+  // Horizontal dotted line for the left side of the pinned rectangle
+  x1 = pinned_rect_f.x();
+  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
+  x2 = hovered_rect_f.x();
+  y2 = y1;
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+
+  // Horizontal dotted line for the right side of the pinned rectangle
+  x1 = pinned_rect_f.right();
+  y1 = pinned_rect_f.y() + pinned_rect_f.height() / 2;
+  x2 = hovered_rect_f.right();
+  y2 = y1;
+  canvas->DrawLine(gfx::PointF(x1, y1), gfx::PointF(x2, y2), flags);
+  DrawSizeWithAnyBounds(x1, y1, x2, y2, RectSide::BOTTOM_SIDE, canvas,
+                        render_text);
+}
+
+}  // namespace
+
+OverlayAgentViews::OverlayAgentViews(DOMAgent* dom_agent)
+    : OverlayAgent(dom_agent),
+      show_size_on_canvas_(false),
+      highlight_rect_config_(HighlightRectsConfiguration::NO_DRAW) {}
+
+OverlayAgentViews::~OverlayAgentViews() {}
+
+void OverlayAgentViews::SetPinnedNodeId(int node_id) {
+  pinned_id_ = node_id;
+  frontend()->nodeHighlightRequested(pinned_id_);
+  HighlightNode(pinned_id_, true /* show_size */);
+}
+
+protocol::Response OverlayAgentViews::setInspectMode(
+    const String& in_mode,
+    protocol::Maybe<protocol::Overlay::HighlightConfig> in_highlightConfig) {
+  pinned_id_ = 0;
+  if (in_mode.compare("searchForNode") == 0) {
+    InstallPreTargetHandler();
+  } else if (in_mode.compare("none") == 0) {
+    RemovePreTargetHandler();
+  }
+  return protocol::Response::OK();
+}
+
+protocol::Response OverlayAgentViews::highlightNode(
+    std::unique_ptr<protocol::Overlay::HighlightConfig> highlight_config,
+    protocol::Maybe<int> node_id) {
+  return HighlightNode(node_id.fromJust());
+}
+
+protocol::Response OverlayAgentViews::hideHighlight() {
+  if (layer_for_highlighting_ && layer_for_highlighting_->visible())
+    layer_for_highlighting_->SetVisible(false);
+  return Response::OK();
+}
+
+void OverlayAgentViews::ShowDistancesInHighlightOverlay(int pinned_id,
+                                                        int element_id) {
+  const std::pair<gfx::NativeWindow, gfx::Rect> pair_r2(
+      dom_agent()
+          ->GetElementFromNodeId(element_id)
+          ->GetNodeWindowAndScreenBounds());
+  const std::pair<gfx::NativeWindow, gfx::Rect> pair_r1(
+      dom_agent()
+          ->GetElementFromNodeId(pinned_id)
+          ->GetNodeWindowAndScreenBounds());
+#if defined(OS_MACOSX)
+  // TODO(lgrey): Explain this
+  if (pair_r1.first != pair_r2.first) {
+    pinned_id_ = 0;
+    return;
+  }
+#endif
+  gfx::Rect r2(pair_r2.second);
+  gfx::Rect r1(pair_r1.second);
+  pinned_rect_ = r1;
+
+  is_swap_ = false;
+  if (r1.x() > r2.x()) {
+    is_swap_ = true;
+    std::swap(r1, r2);
+  }
+  if (r1.Contains(r2)) {
+    highlight_rect_config_ = HighlightRectsConfiguration::R1_CONTAINS_R2;
+  } else if (r1.right() <= r2.x()) {
+    if ((r1.y() <= r2.y() && r2.y() <= r1.bottom()) ||
+        (r1.y() <= r2.bottom() && r2.bottom() <= r1.bottom()) ||
+        (r2.y() <= r1.y() && r1.y() <= r2.bottom()) ||
+        (r2.y() <= r1.bottom() && r1.bottom() <= r2.bottom())) {
+      highlight_rect_config_ =
+          HighlightRectsConfiguration::R1_HORIZONTAL_FULL_LEFT_R2;
+    } else if (r1.bottom() <= r2.y()) {
+      highlight_rect_config_ = HighlightRectsConfiguration::R1_TOP_FULL_LEFT_R2;
+    } else if (r1.y() >= r2.bottom()) {
+      highlight_rect_config_ =
+          HighlightRectsConfiguration::R1_BOTTOM_FULL_LEFT_R2;
+    }
+  } else if (r1.x() <= r2.x() && r2.x() <= r1.right()) {
+    if (r1.bottom() <= r2.y()) {
+      highlight_rect_config_ =
+          HighlightRectsConfiguration::R1_TOP_PARTIAL_LEFT_R2;
+    } else if (r1.y() >= r2.bottom()) {
+      highlight_rect_config_ =
+          HighlightRectsConfiguration::R1_BOTTOM_PARTIAL_LEFT_R2;
+    } else if (r1.Intersects(r2)) {
+      highlight_rect_config_ = HighlightRectsConfiguration::R1_INTERSECTS_R2;
+    } else {
+      NOTREACHED();
+    }
+  } else {
+    highlight_rect_config_ = HighlightRectsConfiguration::NO_DRAW;
+  }
+}
+
+Response OverlayAgentViews::HighlightNode(int node_id, bool show_size) {
+  UIElement* element = dom_agent()->GetElementFromNodeId(node_id);
+  if (!element)
+    return Response::Error("No node found with that id");
+
+  if (!layer_for_highlighting_) {
+    layer_for_highlighting_.reset(new ui::Layer(ui::LayerType::LAYER_TEXTURED));
+    layer_for_highlighting_->set_name("HighlightingLayer");
+    layer_for_highlighting_->set_delegate(this);
+    layer_for_highlighting_->SetFillsBoundsOpaquely(false);
+  }
+
+  highlight_rect_config_ = HighlightRectsConfiguration::NO_DRAW;
+  show_size_on_canvas_ = show_size;
+  layer_for_highlighting_->SetVisible(
+      UpdateHighlight(element->GetNodeWindowAndScreenBounds()));
+  return Response::OK();
+}
+
+void OverlayAgentViews::OnMouseEvent(ui::MouseEvent* event) {
+  // Make sure the element tree has been populated before processing
+  // mouse events.
+  if (!dom_agent()->element_root())
+    return;
+
+  // Show parent of the pinned element with id |pinned_id_| when mouse scrolls
+  // up. If parent exists, highlight and re-pin parent element.
+  if (event->type() == ui::ET_MOUSEWHEEL && pinned_id_) {
+    const ui::MouseWheelEvent* mouse_event =
+        static_cast<ui::MouseWheelEvent*>(event);
+    DCHECK(mouse_event);
+    if (mouse_event->y_offset() > 0) {
+      const int parent_node_id = dom_agent()->GetParentIdOfNodeId(pinned_id_);
+      if (parent_node_id)
+        SetPinnedNodeId(parent_node_id);
+      event->SetHandled();
+    } else if (mouse_event->y_offset() < 0) {
+      // TODO(thanhph): discuss behaviours when mouse scrolls down.
+    }
+    return;
+  }
+
+  // Find node id of element whose bounds contain the mouse pointer location.
+  int element_id = FindElementIdTargetedByPoint(event);
+  if (!element_id)
+    return;
+
+#if defined(USE_AURA)
+  aura::Window* target = static_cast<aura::Window*>(event->target());
+  bool active_window = ::wm::IsActiveWindow(
+      target->GetRootWindow()->GetEventHandlerForPoint(event->root_location()));
+#else
+  bool active_window = true;
+#endif
+  if (pinned_id_ == element_id && active_window) {
+    event->SetHandled();
+    return;
+  }
+
+  // Pin the hover element on click.
+  if (event->type() == ui::ET_MOUSE_PRESSED) {
+    if (active_window)
+      event->SetHandled();
+    SetPinnedNodeId(element_id);
+  } else if (pinned_id_) {
+    // If hovering with a pinned element, then show distances between the pinned
+    // element and the hover element.
+    HighlightNode(element_id, false /* show_size */);
+    ShowDistancesInHighlightOverlay(pinned_id_, element_id);
+  } else {
+    // Display only guidelines if hovering without a pinned element.
+    frontend()->nodeHighlightRequested(element_id);
+    HighlightNode(element_id, false /* show_size */);
+  }
+}
+
+void OverlayAgentViews::OnKeyEvent(ui::KeyEvent* event) {
+  if (!dom_agent()->element_root())
+    return;
+
+  // Exit inspect mode by pressing ESC key.
+  if (event->key_code() == ui::KeyboardCode::VKEY_ESCAPE) {
+    RemovePreTargetHandler();
+    if (pinned_id_) {
+      frontend()->inspectNodeRequested(pinned_id_);
+      HighlightNode(pinned_id_, true /* show_size */);
+    }
+    // Unpin element.
+    pinned_id_ = 0;
+  }
+}
+
+void OverlayAgentViews::OnPaintLayer(const ui::PaintContext& context) {
+  const gfx::Rect& screen_bounds(layer_for_highlighting_->bounds());
+  ui::PaintRecorder recorder(context, screen_bounds.size());
+  gfx::Canvas* canvas = recorder.canvas();
+  // Convert the hovered rect from screen coordinates to layer coordinates.
+  gfx::RectF hovered_rect_f(hovered_rect_);
+  hovered_rect_f.Offset(-layer_for_highlighting_screen_offset_);
+
+  cc::PaintFlags flags;
+  flags.setStrokeWidth(1.0f);
+  flags.setColor(SK_ColorBLUE);
+  flags.setStyle(cc::PaintFlags::kStroke_Style);
+
+  constexpr SkScalar intervals[] = {1.f, 4.f};
+  flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
+
+  if (!render_text_)
+    render_text_ = gfx::RenderText::CreateHarfBuzzInstance();
+  DrawRulers(screen_bounds, canvas, render_text_.get());
+
+  // Display guide lines if |highlight_rect_config_| is NO_DRAW.
+  if (highlight_rect_config_ == HighlightRectsConfiguration::NO_DRAW) {
+    hovered_rect_f.Inset(gfx::InsetsF(-1));
+    DrawRectGuideLinesOnCanvas(screen_bounds, hovered_rect_f, flags, canvas);
+    // Draw |hovered_rect_f| bounds.
+    flags.setPathEffect(nullptr);
+    canvas->DrawRect(hovered_rect_f, flags);
+
+    // Display size of the rectangle after mouse click.
+    if (show_size_on_canvas_) {
+      DrawSizeOfRectangle(gfx::ToNearestRect(hovered_rect_f),
+                          RectSide::BOTTOM_SIDE, canvas, render_text_.get());
+    }
+    return;
+  }
+  flags.setPathEffect(nullptr);
+  flags.setColor(SK_ColorBLUE);
+
+  // Convert the pinned rect from screen coordinates to layer coordinates.
+  gfx::RectF pinned_rect_f(pinned_rect_);
+  pinned_rect_f.Offset(-layer_for_highlighting_screen_offset_);
+
+  // Draw |pinned_rect_f| bounds in blue.
+  canvas->DrawRect(pinned_rect_f, flags);
+
+  // Draw |hovered_rect_f| bounds in green.
+  flags.setColor(SK_ColorGREEN);
+  canvas->DrawRect(hovered_rect_f, flags);
+
+  // Draw distances in red colour.
+  flags.setPathEffect(nullptr);
+  flags.setColor(SK_ColorRED);
+
+  // Make sure |pinned_rect_f| stays on the right or below of |hovered_rect_f|.
+  if (pinned_rect_f.x() < hovered_rect_f.x() ||
+      (pinned_rect_f.x() == hovered_rect_f.x() &&
+       pinned_rect_f.y() < hovered_rect_f.y())) {
+    std::swap(pinned_rect_f, hovered_rect_f);
+  }
+
+  switch (highlight_rect_config_) {
+    case HighlightRectsConfiguration::R1_CONTAINS_R2:
+      DrawR1ContainsR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                       render_text_.get());
+      return;
+    case HighlightRectsConfiguration::R1_HORIZONTAL_FULL_LEFT_R2:
+      DrawR1HorizontalFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                                 render_text_.get());
+      return;
+    case HighlightRectsConfiguration::R1_TOP_FULL_LEFT_R2:
+      DrawR1TopFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                          render_text_.get());
+
+      // Draw 4 guide lines along distance lines.
+      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
+
+      // Bottom horizontal dotted line from left to right.
+      canvas->DrawLine(
+          gfx::PointF(0.0f, hovered_rect_f.bottom()),
+          gfx::PointF(screen_bounds.right(), hovered_rect_f.bottom()), flags);
+
+      // Right vertical dotted line from top to bottom.
+      canvas->DrawLine(
+          gfx::PointF(hovered_rect_f.right(), 0.0f),
+          gfx::PointF(hovered_rect_f.right(), screen_bounds.bottom()), flags);
+
+      // Top horizontal dotted line from left to right.
+      canvas->DrawLine(gfx::PointF(0.0f, pinned_rect_f.y()),
+                       gfx::PointF(screen_bounds.right(), pinned_rect_f.y()),
+                       flags);
+
+      // Left vertical dotted line from top to bottom.
+      canvas->DrawLine(gfx::PointF(pinned_rect_f.x(), 0.0f),
+                       gfx::PointF(pinned_rect_f.x(), screen_bounds.bottom()),
+                       flags);
+      return;
+    case HighlightRectsConfiguration::R1_BOTTOM_FULL_LEFT_R2:
+      DrawR1BottomFullLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                             render_text_.get());
+
+      // Draw 2 guide lines along distance lines.
+      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
+
+      // Top horizontal dotted line from left to right.
+      canvas->DrawLine(
+          gfx::PointF(0.0f, pinned_rect_f.bottom()),
+          gfx::PointF(screen_bounds.right(), pinned_rect_f.bottom()), flags);
+
+      // Left vertical dotted line from top to bottom.
+      canvas->DrawLine(gfx::PointF(pinned_rect_f.x(), 0.0f),
+                       gfx::PointF(pinned_rect_f.x(), screen_bounds.bottom()),
+                       flags);
+      return;
+    case HighlightRectsConfiguration::R1_TOP_PARTIAL_LEFT_R2:
+      DrawR1TopPartialLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                             render_text_.get());
+
+      // Draw 1 guide line along distance lines.
+      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
+
+      // Top horizontal dotted line from left to right.
+      canvas->DrawLine(gfx::PointF(0.0f, pinned_rect_f.y()),
+                       gfx::PointF(screen_bounds.right(), pinned_rect_f.y()),
+                       flags);
+      return;
+    case HighlightRectsConfiguration::R1_BOTTOM_PARTIAL_LEFT_R2:
+      DrawR1BottomPartialLeftR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                                render_text_.get());
+      return;
+    case HighlightRectsConfiguration::R1_INTERSECTS_R2:
+      DrawR1IntersectsR2(pinned_rect_f, hovered_rect_f, flags, canvas,
+                         render_text_.get());
+      // Draw 4 guide line along distance lines.
+      flags.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
+
+      DrawRectGuideLinesOnCanvas(screen_bounds, hovered_rect_f, flags, canvas);
+      return;
+    default:
+      NOTREACHED();
+      return;
+  }
+}
+
+bool OverlayAgentViews::UpdateHighlight(
+    const std::pair<gfx::NativeWindow, gfx::Rect>& window_and_bounds) {
+  if (window_and_bounds.second.IsEmpty()) {
+    hovered_rect_.SetRect(0, 0, 0, 0);
+    return false;
+  }
+  ui::Layer* root_layer = nullptr;
+#if defined(OS_MACOSX)
+  views::Widget* widget =
+      views::Widget::GetWidgetForNativeWindow(window_and_bounds.first);
+  root_layer = widget->GetLayer();
+  layer_for_highlighting_screen_offset_ =
+      widget->GetContentsView()->GetBoundsInScreen().OffsetFromOrigin();
+#else
+  gfx::NativeWindow root = window_and_bounds.first->GetRootWindow();
+  root_layer = root->layer();
+#if defined(OS_CHROMEOS)
+  // Get the screen's display-root window; otherwise, if the window belongs to
+  // a window service client, |root| will only be a client-root window.
+  aura::Window* window = display::Screen::GetScreen()->GetWindowAtScreenPoint(
+      root->GetBoundsInScreen().origin());
+  if (window)  // May be null in unit tests.
+    root = window->GetRootWindow();
+#endif  // OS_CHROMEOS
+  layer_for_highlighting_screen_offset_ =
+      root->GetBoundsInScreen().OffsetFromOrigin();
+#endif  // defined(OS_MACOSX)
+  DCHECK(root_layer);
+
+  layer_for_highlighting_->SetBounds(root_layer->bounds());
+  layer_for_highlighting_->SchedulePaint(root_layer->bounds());
+
+  if (root_layer != layer_for_highlighting_->parent())
+    root_layer->Add(layer_for_highlighting_.get());
+  else
+    root_layer->StackAtTop(layer_for_highlighting_.get());
+
+  hovered_rect_ = window_and_bounds.second;
+  return true;
+}
+
+}  // namespace ui_devtools
diff --git a/components/ui_devtools/views/overlay_agent_views.h b/components/ui_devtools/views/overlay_agent_views.h
new file mode 100644
index 0000000..7a86910
--- /dev/null
+++ b/components/ui_devtools/views/overlay_agent_views.h
@@ -0,0 +1,129 @@
+// Copyright 2018 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 COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_VIEWS_H_
+#define COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_VIEWS_H_
+
+#include <vector>
+
+#include "components/ui_devtools/Overlay.h"
+#include "components/ui_devtools/overlay_agent.h"
+#include "components/ui_devtools/views/dom_agent_views.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_delegate.h"
+#include "ui/events/event.h"
+#include "ui/events/event_handler.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class RenderText;
+}
+
+namespace ui_devtools {
+
+enum HighlightRectsConfiguration {
+  NO_DRAW,
+  R1_CONTAINS_R2,
+  R1_HORIZONTAL_FULL_LEFT_R2,
+  R1_TOP_FULL_LEFT_R2,
+  R1_BOTTOM_FULL_LEFT_R2,
+  R1_TOP_PARTIAL_LEFT_R2,
+  R1_BOTTOM_PARTIAL_LEFT_R2,
+  R1_INTERSECTS_R2
+};
+
+enum RectSide { TOP_SIDE, LEFT_SIDE, RIGHT_SIDE, BOTTOM_SIDE };
+
+class OverlayAgentViews : public OverlayAgent,
+                          public ui::EventHandler,
+                          public ui::LayerDelegate {
+ public:
+  ~OverlayAgentViews() override;
+
+  // Creates a platform-specific instance.
+  static std::unique_ptr<OverlayAgentViews> Create(DOMAgent* dom_agent);
+
+  int pinned_id() const { return pinned_id_; }
+  void SetPinnedNodeId(int pinned_id);
+
+  // Overlay::Backend:
+  protocol::Response setInspectMode(
+      const protocol::String& in_mode,
+      protocol::Maybe<protocol::Overlay::HighlightConfig> in_highlightConfig)
+      override;
+  protocol::Response highlightNode(
+      std::unique_ptr<protocol::Overlay::HighlightConfig> highlight_config,
+      protocol::Maybe<int> node_id) override;
+  protocol::Response hideHighlight() override;
+
+  HighlightRectsConfiguration highlight_rect_config() const {
+    return highlight_rect_config_;
+  }
+
+  // Return the id of the UI element located at |event|'s root location.
+  // The function first searches for the targeted window, then the targeted
+  // widget (if one exists), then the targeted view (if one exists). Return 0 if
+  // no valid target is found.
+  virtual int FindElementIdTargetedByPoint(ui::LocatedEvent* event) const = 0;
+
+ protected:
+  OverlayAgentViews(DOMAgent* dom_agent);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest,
+                           MouseEventsGenerateFEEventsInInspectMode);
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightRects);
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightNonexistentNode);
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightWidget);
+#if defined(USE_AURA)
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightWindow);
+  FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightEmptyOrInvisibleWindow);
+#endif
+
+  // Start handling events intended for inspectable elements.
+  virtual void InstallPreTargetHandler() = 0;
+  // Stop handling events intended for inspectable elements.
+  virtual void RemovePreTargetHandler() = 0;
+
+  protocol::Response HighlightNode(int node_id, bool show_size = false);
+  // Returns true when there is any visible element to highlight.
+  bool UpdateHighlight(
+      const std::pair<gfx::NativeWindow, gfx::Rect>& window_and_screen_bounds);
+
+  // Shows the distances between the nodes identified by |pinned_id| and
+  // |element_id| in the highlight overlay.
+  void ShowDistancesInHighlightOverlay(int pinned_id, int element_id);
+
+  // ui:EventHandler:
+  void OnMouseEvent(ui::MouseEvent* event) override;
+  void OnKeyEvent(ui::KeyEvent* event) override;
+
+  // ui::LayerDelegate:
+  void OnPaintLayer(const ui::PaintContext& context) override;
+  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
+                                  float new_device_scale_factor) override {}
+
+  ui::Layer* layer_for_highlighting() { return layer_for_highlighting_.get(); }
+
+  std::unique_ptr<gfx::RenderText> render_text_;
+  bool show_size_on_canvas_ = false;
+  HighlightRectsConfiguration highlight_rect_config_;
+  bool is_swap_ = false;
+
+  // The layer used to paint highlights, and its offset from the screen origin.
+  std::unique_ptr<ui::Layer> layer_for_highlighting_;
+  gfx::Vector2d layer_for_highlighting_screen_offset_;
+
+  // Hovered and pinned element bounds in screen coordinates; empty if none.
+  gfx::Rect hovered_rect_;
+  gfx::Rect pinned_rect_;
+
+  int pinned_id_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(OverlayAgentViews);
+};
+
+}  // namespace ui_devtools
+
+#endif  // COMPONENTS_UI_DEVTOOLS_VIEWS_OVERLAY_AGENT_VIEWS_H_
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index f31146d..a0b9023 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -36,7 +36,7 @@
   OBSOLETE_RWH_FOCUS = 9,
   OBSOLETE_RWH_BLUR = 10,
   RWH_SHARED_BITMAP = 11,
-  RWH_BAD_ACK_MESSAGE = 12,
+  OBSOLETE_RWH_BAD_ACK_MESSAGE = 12,
   OBSOLETE_RWHVA_SHARED_MEMORY = 13,
   SERVICE_WORKER_BAD_URL = 14,
   OBSOLETE_WC_INVALID_FRAME_SOURCE = 15,
diff --git a/content/browser/renderer_host/input/input_disposition_handler.h b/content/browser/renderer_host/input/input_disposition_handler.h
index 07d5e50b..bee5641 100644
--- a/content/browser/renderer_host/input/input_disposition_handler.h
+++ b/content/browser/renderer_host/input/input_disposition_handler.h
@@ -28,13 +28,6 @@
   virtual void OnGestureEventAck(const GestureEventWithLatencyInfo& event,
                                  InputEventAckSource ack_source,
                                  InputEventAckState ack_result) = 0;
-
-  enum UnexpectedEventAckType {
-    UNEXPECTED_ACK,
-    UNEXPECTED_EVENT_TYPE,
-    BAD_ACK_MESSAGE
-  };
-  virtual void OnUnexpectedEventAck(UnexpectedEventAckType type) = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/input/mock_input_disposition_handler.cc b/content/browser/renderer_host/input/mock_input_disposition_handler.cc
index fd03745..ad8f1fc 100644
--- a/content/browser/renderer_host/input/mock_input_disposition_handler.cc
+++ b/content/browser/renderer_host/input/mock_input_disposition_handler.cc
@@ -21,7 +21,6 @@
 MockInputDispositionHandler::MockInputDispositionHandler()
     : input_router_(nullptr),
       ack_count_(0),
-      unexpected_event_ack_called_(false),
       ack_event_type_(WebInputEvent::kUndefined),
       ack_state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {}
 
@@ -71,12 +70,6 @@
   RecordAckCalled(event.event.GetType(), ack_result);
 }
 
-void MockInputDispositionHandler::OnUnexpectedEventAck(
-    UnexpectedEventAckType type) {
-  VLOG(1) << __FUNCTION__ << " called!";
-  unexpected_event_ack_called_ = true;
-}
-
 size_t MockInputDispositionHandler::GetAndResetAckCount() {
   size_t ack_count = ack_count_;
   ack_count_ = 0;
diff --git a/content/browser/renderer_host/input/mock_input_disposition_handler.h b/content/browser/renderer_host/input/mock_input_disposition_handler.h
index ad79282..7cc96a1 100644
--- a/content/browser/renderer_host/input/mock_input_disposition_handler.h
+++ b/content/browser/renderer_host/input/mock_input_disposition_handler.h
@@ -35,7 +35,6 @@
   void OnGestureEventAck(const GestureEventWithLatencyInfo& event,
                          InputEventAckSource ack_source,
                          InputEventAckState ack_result) override;
-  void OnUnexpectedEventAck(UnexpectedEventAckType type) override;
 
   size_t GetAndResetAckCount();
 
@@ -53,9 +52,6 @@
     touch_followup_event_ = std::move(event);
   }
 
-  bool unexpected_event_ack_called() const {
-    return unexpected_event_ack_called_;
-  }
   InputEventAckState ack_state() const { return ack_state_; }
 
   InputEventAckState acked_wheel_event_state() const {
@@ -91,7 +87,6 @@
   InputRouter* input_router_;
 
   size_t ack_count_;
-  bool unexpected_event_ack_called_;
   blink::WebInputEvent::Type ack_event_type_;
   InputEventAckState ack_state_;
   InputEventAckState acked_wheel_event_state_;
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 5434939..8e869f1 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2799,14 +2799,6 @@
     view_->ProcessAckedTouchEvent(event, ack_result);
 }
 
-void RenderWidgetHostImpl::OnUnexpectedEventAck(UnexpectedEventAckType type) {
-  if (type == BAD_ACK_MESSAGE) {
-    bad_message::ReceivedBadMessage(process_, bad_message::RWH_BAD_ACK_MESSAGE);
-  } else if (type == UNEXPECTED_EVENT_TYPE) {
-    suppress_events_until_keydown_ = false;
-  }
-}
-
 bool RenderWidgetHostImpl::IsIgnoringInputEvents() const {
   return process_->IsBlocked() || !delegate_ ||
          delegate_->ShouldIgnoreInputEvents();
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index cb42577e..4a3b001b 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -759,7 +759,6 @@
   void OnGestureEventAck(const GestureEventWithLatencyInfo& event,
                          InputEventAckSource ack_source,
                          InputEventAckState ack_result) override;
-  void OnUnexpectedEventAck(UnexpectedEventAckType type) override;
 
   // virtual for testing.
   virtual void OnMouseEventAck(const MouseEventWithLatencyInfo& event,
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index 704ae6c..594454d 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -388,13 +388,6 @@
   request.SetExtraData(std::move(extra_data));
   request.SetAppCacheHostID(appcache_host_id_);
 
-  if (IsControlledByServiceWorker() ==
-      blink::mojom::ControllerServiceWorkerMode::kNoController) {
-    // TODO(falken): Is still this needed? It used to set kForeign for foreign
-    // fetch.
-    request.SetSkipServiceWorker(true);
-  }
-
   if (g_rewrite_url)
     request.SetURL(g_rewrite_url(request.Url().GetString().Utf8(), false));
 
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
index 3742531..f8909fa 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
+++ b/content/renderer/service_worker/service_worker_network_provider_for_frame.cc
@@ -99,17 +99,6 @@
   auto* extra_data = static_cast<RequestExtraData*>(request.GetExtraData());
   extra_data->set_service_worker_provider_id(provider_id());
 
-  // If the provider does not have a controller at this point, the renderer
-  // expects the request to never be handled by a service worker, so call
-  // SetSkipServiceWorker() with true to skip service workers here. Otherwise,
-  // a service worker that is in the process of becoming the controller (i.e.,
-  // via claim()) on the browser-side could handle the request and break the
-  // assumptions of the renderer.
-  if (IsControlledByServiceWorker() ==
-      blink::mojom::ControllerServiceWorkerMode::kNoController) {
-    request.SetSkipServiceWorker(true);
-  }
-
   // Inject this frame's fetch window id into the request.
   if (context())
     request.SetFetchWindowId(context()->fetch_request_window_id());
diff --git a/device/fido/ctap2_device_operation.h b/device/fido/ctap2_device_operation.h
index 4bda200..5de6f4e 100644
--- a/device/fido/ctap2_device_operation.h
+++ b/device/fido/ctap2_device_operation.h
@@ -134,8 +134,9 @@
       cbor::Reader::DecoderError error;
       cbor = cbor::Reader::Read(cbor_bytes, &error);
       if (!cbor) {
-        FIDO_LOG(ERROR) << "-> (CBOR parse error " << static_cast<int>(error)
-                        << " from "
+        FIDO_LOG(ERROR) << "-> (CBOR parse error '"
+                        << cbor::Reader::ErrorCodeToString(error)
+                        << "' from raw message "
                         << base::HexEncode(device_response->data(),
                                            device_response->size())
                         << ")";
diff --git a/device/fido/device_response_converter.cc b/device/fido/device_response_converter.cc
index bff3fe5..5180dd5f 100644
--- a/device/fido/device_response_converter.cc
+++ b/device/fido/device_response_converter.cc
@@ -12,6 +12,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/optional.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "components/cbor/diagnostic_writer.h"
 #include "components/cbor/reader.h"
 #include "components/cbor/writer.h"
@@ -142,9 +143,19 @@
       GetResponseCode(buffer) != CtapDeviceResponseCode::kSuccess)
     return base::nullopt;
 
-  base::Optional<CBOR> decoded_response = cbor::Reader::Read(buffer.subspan(1));
+  cbor::Reader::DecoderError error;
+  base::Optional<CBOR> decoded_response =
+      cbor::Reader::Read(buffer.subspan(1), &error);
 
-  if (!decoded_response || !decoded_response->is_map())
+  if (!decoded_response) {
+    FIDO_LOG(ERROR) << "-> (CBOR parse error from GetInfo response '"
+                    << cbor::Reader::ErrorCodeToString(error)
+                    << "' from raw message "
+                    << base::HexEncode(buffer.data(), buffer.size()) << ")";
+    return base::nullopt;
+  }
+
+  if (!decoded_response->is_map())
     return base::nullopt;
 
   FIDO_LOG(DEBUG) << "-> " << cbor::DiagnosticWriter::Write(*decoded_response);
diff --git a/ios/chrome/browser/providers/chromium_browser_provider.h b/ios/chrome/browser/providers/chromium_browser_provider.h
index f5f824d..c35603f5 100644
--- a/ios/chrome/browser/providers/chromium_browser_provider.h
+++ b/ios/chrome/browser/providers/chromium_browser_provider.h
@@ -21,9 +21,8 @@
   UITextField<TextFieldStyling>* CreateStyledTextField(
       CGRect frame) const override NS_RETURNS_RETAINED;
   VoiceSearchProvider* GetVoiceSearchProvider() const override;
-  id<LogoVendor> CreateLogoVendor(ios::ChromeBrowserState* browser_state,
-                                  id<UrlLoader> loader) const override
-      NS_RETURNS_RETAINED;
+  id<LogoVendor> CreateLogoVendor(ios::ChromeBrowserState* browser_state)
+      const override NS_RETURNS_RETAINED;
   UserFeedbackProvider* GetUserFeedbackProvider() const override;
   AppDistributionProvider* GetAppDistributionProvider() const override;
   BrandedImageProvider* GetBrandedImageProvider() const override;
diff --git a/ios/chrome/browser/providers/chromium_browser_provider.mm b/ios/chrome/browser/providers/chromium_browser_provider.mm
index f5ddfc1d..91ad3a5 100644
--- a/ios/chrome/browser/providers/chromium_browser_provider.mm
+++ b/ios/chrome/browser/providers/chromium_browser_provider.mm
@@ -69,8 +69,7 @@
 }
 
 id<LogoVendor> ChromiumBrowserProvider::CreateLogoVendor(
-    ios::ChromeBrowserState* browser_state,
-    id<UrlLoader> loader) const {
+    ios::ChromeBrowserState* browser_state) const {
   return [[ChromiumLogoController alloc] init];
 }
 
diff --git a/ios/chrome/browser/ui/BUILD.gn b/ios/chrome/browser/ui/BUILD.gn
index 6a4f18c..bce76c6 100644
--- a/ios/chrome/browser/ui/BUILD.gn
+++ b/ios/chrome/browser/ui/BUILD.gn
@@ -11,7 +11,6 @@
     "native_content_controller.h",
     "native_content_controller.mm",
     "prerender_final_status.h",
-    "url_loader.h",
   ]
   public_deps = [
     ":network_activity_indicator_manager",
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index cff198a..6a56b7cf 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -123,8 +123,7 @@
                                self.browserState)
          urlLoadingService:urlLoadingService
                 logoVendor:ios::GetChromeBrowserProvider()->CreateLogoVendor(
-                               self.browserState,
-                               urlLoadingService->GetUrlLoader())];
+                               self.browserState)];
 
   BOOL voiceSearchEnabled = ios::GetChromeBrowserProvider()
                                 ->GetVoiceSearchProvider()
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index 66eff5f..7a58da0 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -36,4 +36,4 @@
                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kOmniboxUseDefaultSearchEngineFavicon{
-    "OmniboxUseDefaultSearchEngineFavicon", base::FEATURE_ENABLED_BY_DEFAULT};
+    "OmniboxUseDefaultSearchEngineFavicon", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/ui/url_loader.h b/ios/chrome/browser/ui/url_loader.h
deleted file mode 100644
index cbba6bd..0000000
--- a/ios/chrome/browser/ui/url_loader.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 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 IOS_CHROME_BROWSER_UI_URL_LOADER_H_
-#define IOS_CHROME_BROWSER_UI_URL_LOADER_H_
-
-#import <Foundation/Foundation.h>
-
-#import "ios/chrome/browser/ui/chrome_load_params.h"
-
-@class OpenNewTabCommand;
-
-namespace sessions {
-struct SessionTab;
-}
-
-@protocol UrlLoader
-
-// Load a new request.
-// TODO(crbug.com/907527): Check if it is possible to merge with the other way
-// of loading a URL.
-- (void)loadURLWithParams:(const ChromeLoadParams&)params;
-
-// Load a new URL on a new page/tab. The |referrer| is optional. The tab will be
-// placed in the model according to |appendTo|. |originPoint| is used when the
-// tab is opened in background as the origin point for the animation, it is not
-// used if the tab is opened in foreground.
-// TODO(crbug.com/907527): Check if it is possible to merge with the other way
-// of loading a URL.
-- (void)webPageOrderedOpen:(OpenNewTabCommand*)command;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_URL_LOADER_H_
diff --git a/ios/chrome/browser/url_loading/url_loading_service.h b/ios/chrome/browser/url_loading/url_loading_service.h
index f5954f7..765d5ba8 100644
--- a/ios/chrome/browser/url_loading/url_loading_service.h
+++ b/ios/chrome/browser/url_loading/url_loading_service.h
@@ -9,7 +9,6 @@
 #import <UIKit/UIKit.h>
 
 #include "components/keyed_service/core/keyed_service.h"
-#import "ios/chrome/browser/ui/url_loader.h"
 #import "ios/web/public/navigation_manager.h"
 #include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
@@ -40,11 +39,8 @@
   void SetDelegate(id<URLLoadingServiceDelegate> delegate);
   void SetBrowser(Browser* browser);
 
-  // TODO(crbug.com/907527): deprecate this when possible.
-  id<UrlLoader> GetUrlLoader();
-
   // Opens a url depending on |params.disposition|.  Applies load strategy then
-  // calls| Dispatch|.
+  // calls |Dispatch|.
   virtual void Load(const UrlLoadParams& params);
 
  private:
@@ -64,7 +60,6 @@
   AppUrlLoadingService* app_service_;
   Browser* browser_;
   UrlLoadingNotifier* notifier_;
-  id<UrlLoader> url_loader_;
 };
 
 #endif  // IOS_CHROME_BROWSER_URL_LOADING_URL_LOADING_SERVICE_H_
diff --git a/ios/chrome/browser/url_loading/url_loading_service.mm b/ios/chrome/browser/url_loading/url_loading_service.mm
index f2ed0c8..2e3109d1 100644
--- a/ios/chrome/browser/url_loading/url_loading_service.mm
+++ b/ios/chrome/browser/url_loading/url_loading_service.mm
@@ -61,47 +61,6 @@
 }
 }
 
-@interface UrlLoadingServiceUrlLoader : NSObject <UrlLoader>
-- (instancetype)initWithUrlLoadingService:(UrlLoadingService*)service;
-@end
-
-@implementation UrlLoadingServiceUrlLoader {
-  UrlLoadingService* service_;
-}
-
-- (instancetype)initWithUrlLoadingService:(UrlLoadingService*)service {
-  DCHECK(service);
-  if (self = [super init]) {
-    service_ = service;
-  }
-  return self;
-}
-
-- (void)loadURLWithParams:(const ChromeLoadParams&)chromeParams {
-  web::NavigationManager::WebLoadParams params = chromeParams.web_params;
-  if (chromeParams.disposition == WindowOpenDisposition::SWITCH_TO_TAB) {
-    service_->Load(UrlLoadParams::SwitchToTab(chromeParams.web_params));
-  } else {
-    service_->Load(UrlLoadParams::InCurrentTab(chromeParams.web_params));
-  }
-}
-
-- (void)webPageOrderedOpen:(OpenNewTabCommand*)command {
-  UrlLoadParams params =
-      UrlLoadParams::InNewTab(command.URL, command.virtualURL);
-  params.SetInBackground(command.inBackground);
-  params.web_params.referrer = command.referrer;
-  params.in_incognito = command.inIncognito;
-  params.append_to = command.appendTo;
-  params.origin_point = command.originPoint;
-  params.from_chrome = command.fromChrome;
-  params.user_initiated = command.userInitiated;
-  params.should_focus_omnibox = command.shouldFocusOmnibox;
-  service_->Load(params);
-}
-
-@end
-
 UrlLoadingService::UrlLoadingService(UrlLoadingNotifier* notifier)
     : notifier_(notifier) {}
 
@@ -316,11 +275,3 @@
     [delegate_ animateOpenBackgroundTabFromParams:params completion:openTab];
   }
 }
-
-id<UrlLoader> UrlLoadingService::GetUrlLoader() {
-  if (!url_loader_) {
-    url_loader_ =
-        [[UrlLoadingServiceUrlLoader alloc] initWithUrlLoadingService:this];
-  }
-  return url_loader_;
-}
diff --git a/ios/chrome/test/fakes/BUILD.gn b/ios/chrome/test/fakes/BUILD.gn
index 79551ce..f321093 100644
--- a/ios/chrome/test/fakes/BUILD.gn
+++ b/ios/chrome/test/fakes/BUILD.gn
@@ -29,8 +29,6 @@
     "fake_store_kit_launcher.mm",
     "fake_ui_view_controller.h",
     "fake_ui_view_controller.mm",
-    "fake_url_loader.h",
-    "fake_url_loader.mm",
   ]
 
   deps = [
diff --git a/ios/chrome/test/fakes/fake_url_loader.h b/ios/chrome/test/fakes/fake_url_loader.h
deleted file mode 100644
index 676ab8a..0000000
--- a/ios/chrome/test/fakes/fake_url_loader.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 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 IOS_CHROME_TEST_FAKES_FAKE_URL_LOADER_H_
-#define IOS_CHROME_TEST_FAKES_FAKE_URL_LOADER_H_
-
-#import "ios/chrome/browser/ui/url_loader.h"
-
-// URLLoader which captures argument passed into loadURLWithParams: and
-// webPageOrderedOpen:referrer:inIncognito:inBackground:originPoint:appendTo:.
-@interface FakeURLLoader : NSObject<UrlLoader>
-
-// These properties capture argumenents passed into protocol methods.
-@property(nonatomic, readonly) const GURL& url;
-@property(nonatomic, readonly) const web::Referrer& referrer;
-@property(nonatomic, readonly) ui::PageTransition transition;
-@property(nonatomic, readonly) BOOL rendererInitiated;
-@property(nonatomic, readonly) BOOL inIncognito;
-@property(nonatomic, readonly, copy) NSDictionary* extraHeaders;
-@property(nonatomic, readonly) WindowOpenDisposition disposition;
-
-@end
-
-#endif  // IOS_CHROME_TEST_FAKES_FAKE_URL_LOADER_H_
diff --git a/ios/chrome/test/fakes/fake_url_loader.mm b/ios/chrome/test/fakes/fake_url_loader.mm
deleted file mode 100644
index f676948..0000000
--- a/ios/chrome/test/fakes/fake_url_loader.mm
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/test/fakes/fake_url_loader.h"
-
-#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface FakeURLLoader () {
-  GURL _url;
-  web::Referrer _referrer;
-}
-
-@property(nonatomic) ui::PageTransition transition;
-@property(nonatomic) BOOL rendererInitiated;
-@property(nonatomic) BOOL inIncognito;
-@property(nonatomic, copy) NSDictionary* extraHeaders;
-@property(nonatomic) WindowOpenDisposition disposition;
-@end
-
-@implementation FakeURLLoader
-
-@synthesize transition = _transition;
-@synthesize rendererInitiated = _rendererInitiated;
-@synthesize inIncognito = _inIncognito;
-@synthesize extraHeaders = _extraHeaders;
-
-- (void)loadURLWithParams:(const ChromeLoadParams&)chromeParams {
-  web::NavigationManager::WebLoadParams params = chromeParams.web_params;
-  _url = params.url;
-  _referrer = params.referrer;
-  self.transition = params.transition_type;
-  self.rendererInitiated = params.is_renderer_initiated;
-  self.extraHeaders = params.extra_headers;
-  self.disposition = chromeParams.disposition;
-}
-
-- (void)webPageOrderedOpen:(OpenNewTabCommand*)command {
-  _url = command.URL;
-  _referrer = command.referrer;
-  self.inIncognito = command.inIncognito;
-}
-
-- (const GURL&)url {
-  return _url;
-}
-
-- (const web::Referrer&)referrer {
-  return _referrer;
-}
-
-@end
diff --git a/ios/public/provider/chrome/browser/chrome_browser_provider.h b/ios/public/provider/chrome/browser/chrome_browser_provider.h
index 732d4a1..785cea0 100644
--- a/ios/public/provider/chrome/browser/chrome_browser_provider.h
+++ b/ios/public/provider/chrome/browser/chrome_browser_provider.h
@@ -41,7 +41,6 @@
 @class TabModel;
 @class UITextField;
 @class UIView;
-@protocol UrlLoader;
 
 namespace ios {
 
@@ -131,10 +130,6 @@
   // Creates and returns an object that can fetch and vend search engine logos.
   // The caller assumes ownership of the returned object.
   virtual id<LogoVendor> CreateLogoVendor(
-      ios::ChromeBrowserState* browser_state,
-      id<UrlLoader> loader) const NS_RETURNS_RETAINED;
-
-  virtual id<LogoVendor> CreateLogoVendor(
       ios::ChromeBrowserState* browser_state) const NS_RETURNS_RETAINED;
 
   // Returns an instance of the omaha service provider.
diff --git a/ios/public/provider/chrome/browser/chrome_browser_provider.mm b/ios/public/provider/chrome/browser/chrome_browser_provider.mm
index 89359c0..26c5bba 100644
--- a/ios/public/provider/chrome/browser/chrome_browser_provider.mm
+++ b/ios/public/provider/chrome/browser/chrome_browser_provider.mm
@@ -89,12 +89,6 @@
 }
 
 id<LogoVendor> ChromeBrowserProvider::CreateLogoVendor(
-    ios::ChromeBrowserState* browser_state,
-    id<UrlLoader> loader) const {
-  return nil;
-}
-
-id<LogoVendor> ChromeBrowserProvider::CreateLogoVendor(
     ios::ChromeBrowserState* browser_state) const {
   return nil;
 }
diff --git a/ios/web_view/internal/sync/cwv_sync_controller.mm b/ios/web_view/internal/sync/cwv_sync_controller.mm
index b9318ac..4e5d5302 100644
--- a/ios/web_view/internal/sync/cwv_sync_controller.mm
+++ b/ios/web_view/internal/sync/cwv_sync_controller.mm
@@ -115,6 +115,7 @@
 }
 
 @synthesize delegate = _delegate;
+@synthesize currentIdentity = _currentIdentity;
 
 - (instancetype)initWithSyncService:(syncer::SyncService*)syncService
                     identityManager:(identity::IdentityManager*)identityManager
@@ -148,18 +149,6 @@
 
 #pragma mark - Public Methods
 
-- (CWVIdentity*)currentIdentity {
-  if (!_identityManager->HasPrimaryAccount()) {
-    return nil;
-  }
-  AccountInfo accountInfo = _identityManager->GetPrimaryAccountInfoDeprecated();
-  NSString* email = base::SysUTF8ToNSString(accountInfo.email);
-  NSString* fullName = base::SysUTF8ToNSString(accountInfo.full_name);
-  NSString* gaiaID = base::SysUTF8ToNSString(accountInfo.gaia);
-  return
-      [[CWVIdentity alloc] initWithEmail:email fullName:fullName gaiaID:gaiaID];
-}
-
 - (BOOL)isPassphraseNeeded {
   return _syncService->GetUserSettings()->IsPassphraseRequiredForDecryption();
 }
@@ -168,13 +157,14 @@
                    dataSource:
                        (__weak id<CWVSyncControllerDataSource>)dataSource {
   DCHECK(!_dataSource);
+  DCHECK(!_currentIdentity);
 
   _dataSource = dataSource;
+  _currentIdentity = identity;
 
   AccountInfo info;
   info.gaia = base::SysNSStringToUTF8(identity.gaiaID);
   info.email = base::SysNSStringToUTF8(identity.email);
-  info.full_name = base::SysNSStringToUTF8(identity.fullName);
   std::string newAuthenticatedAccountID =
       _identityManager->LegacySeedAccountInfo(info);
   auto* primaryAccountMutator = _identityManager->GetPrimaryAccountMutator();
@@ -189,6 +179,7 @@
       identity::PrimaryAccountMutator::ClearAccountsAction::kDefault,
       signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS,
       signin_metrics::SignoutDelete::IGNORE_METRIC);
+  _currentIdentity = nil;
   _dataSource = nil;
 }
 
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 469ff1a..31f36b3 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -387,14 +387,6 @@
     uint8_t* u_data,
     uint8_t* v_data,
     base::TimeDelta timestamp) {
-  const StorageType storage = STORAGE_UNOWNED_MEMORY;
-  if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
-    DLOG(ERROR) << __func__ << " Invalid config."
-                << ConfigToString(format, storage, coded_size, visible_rect,
-                                  natural_size);
-    return nullptr;
-  }
-
   const size_t height = coded_size.height();
   auto layout = VideoFrameLayout::CreateWithStrides(
       format, coded_size, {y_stride, u_stride, v_stride},
@@ -405,8 +397,36 @@
     return nullptr;
   }
 
+  return WrapExternalYuvDataWithLayout(*layout, visible_rect, natural_size,
+                                       y_data, u_data, v_data, timestamp);
+}
+
+// static
+scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvDataWithLayout(
+    const VideoFrameLayout& layout,
+    const gfx::Rect& visible_rect,
+    const gfx::Size& natural_size,
+    uint8_t* y_data,
+    uint8_t* u_data,
+    uint8_t* v_data,
+    base::TimeDelta timestamp) {
+  const StorageType storage = STORAGE_UNOWNED_MEMORY;
+  const VideoPixelFormat format = layout.format();
+  if (!IsValidConfig(format, storage, layout.coded_size(), visible_rect,
+                     natural_size)) {
+    DLOG(ERROR) << __func__ << " Invalid config."
+                << ConfigToString(format, storage, layout.coded_size(),
+                                  visible_rect, natural_size);
+    return nullptr;
+  }
+  if (!IsYuvPlanar(format)) {
+    DLOG(ERROR) << __func__ << " Format is not YUV. " << format;
+    return nullptr;
+  }
+
+  DCHECK_LE(NumPlanes(format), 3u);
   scoped_refptr<VideoFrame> frame(
-      new VideoFrame(*layout, storage, visible_rect, natural_size, timestamp));
+      new VideoFrame(layout, storage, visible_rect, natural_size, timestamp));
   frame->data_[kYPlane] = y_data;
   frame->data_[kUPlane] = u_data;
   frame->data_[kVPlane] = v_data;
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index dd54aa2..5159f6b9 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -228,6 +228,17 @@
       uint8_t* v_data,
       base::TimeDelta timestamp);
 
+  // Wraps external YUV data with VideoFrameLayout. The returned VideoFrame does
+  // not own the data passed in.
+  static scoped_refptr<VideoFrame> WrapExternalYuvDataWithLayout(
+      const VideoFrameLayout& layout,
+      const gfx::Rect& visible_rect,
+      const gfx::Size& natural_size,
+      uint8_t* y_data,
+      uint8_t* u_data,
+      uint8_t* v_data,
+      base::TimeDelta timestamp);
+
   // Wraps external YUVA data of the given parameters with a VideoFrame.
   // The returned VideoFrame does not own the data passed in.
   static scoped_refptr<VideoFrame> WrapExternalYuvaData(
diff --git a/media/gpu/linux/generic_dmabuf_video_frame_mapper.cc b/media/gpu/linux/generic_dmabuf_video_frame_mapper.cc
index 6b8e68b..9f28d09f 100644
--- a/media/gpu/linux/generic_dmabuf_video_frame_mapper.cc
+++ b/media/gpu/linux/generic_dmabuf_video_frame_mapper.cc
@@ -42,19 +42,16 @@
 // |chunks| is the vector of pair of (address, size) to be called in munmap().
 // |src_video_frame| is the video frame that owns dmabufs to the mapped planes.
 scoped_refptr<VideoFrame> CreateMappedVideoFrame(
-    const VideoFrameLayout& layout,
-    const gfx::Rect& visible_rect,
+    scoped_refptr<const VideoFrame> src_video_frame,
     uint8_t* plane_addrs[kNumOfYUVPlanes],
-    const std::vector<std::pair<uint8_t*, size_t>>& chunks,
-    scoped_refptr<const VideoFrame> src_video_frame) {
-  int32_t strides[kNumOfYUVPlanes] = {};
-  for (size_t i = 0; i < layout.num_planes(); i++) {
-    strides[i] = layout.planes()[i].stride;
-  }
-  auto video_frame = VideoFrame::WrapExternalYuvData(
-      layout.format(), layout.coded_size(), visible_rect, visible_rect.size(),
-      strides[0], strides[1], strides[2], plane_addrs[0], plane_addrs[1],
-      plane_addrs[2], base::TimeDelta());
+    const std::vector<std::pair<uint8_t*, size_t>>& chunks) {
+  scoped_refptr<VideoFrame> video_frame;
+
+  const auto& layout = src_video_frame->layout();
+  const auto& visible_rect = src_video_frame->visible_rect();
+  video_frame = VideoFrame::WrapExternalYuvDataWithLayout(
+      layout, visible_rect, visible_rect.size(), plane_addrs[0], plane_addrs[1],
+      plane_addrs[2], src_video_frame->timestamp());
   if (!video_frame) {
     return nullptr;
   }
@@ -76,13 +73,6 @@
   }
 
   const VideoFrameLayout& layout = video_frame->layout();
-  const VideoPixelFormat pixel_format = layout.format();
-  // GenericDmaBufVideoFrameMapper only supports NV12 and YV12 for output
-  // picture format.
-  if (pixel_format != PIXEL_FORMAT_NV12 && pixel_format != PIXEL_FORMAT_YV12) {
-    NOTIMPLEMENTED() << " Unsupported PixelFormat: " << pixel_format;
-    return nullptr;
-  }
 
   // Map all buffers from their start address.
   const auto& dmabuf_fds = video_frame->DmabufFds();
@@ -115,8 +105,7 @@
       plane_addrs[i] = buffer_addrs[i] + planes[i].offset;
     }
   }
-  return CreateMappedVideoFrame(layout, video_frame->visible_rect(),
-                                plane_addrs, chunks, std::move(video_frame));
+  return CreateMappedVideoFrame(std::move(video_frame), plane_addrs, chunks);
 }
 
 }  // namespace media
diff --git a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
index 56279f08..d3007fda 100644
--- a/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
+++ b/media/gpu/vaapi/vaapi_dmabuf_video_frame_mapper.cc
@@ -33,31 +33,40 @@
 }
 
 scoped_refptr<VideoFrame> CreateMappedVideoFrame(
-    const VideoFrameLayout& layout,
+    const VideoPixelFormat format,
     const gfx::Rect& visible_rect,
+    const base::TimeDelta timestamp,
     std::unique_ptr<ScopedVAImage> va_image) {
   // ScopedVAImage manages the resource of mapped data. That is, ScopedVAImage's
   // dtor releases the mapped resource.
-  const size_t num_planes = layout.num_planes();
+  const size_t num_planes = VideoFrame::NumPlanes(format);
   if (num_planes != va_image->image()->num_planes) {
-    VLOGF(1) << "The number of planes is not same between layout and VAImage, "
-             << "(layout: " << num_planes
+    VLOGF(1) << "The number of planes of VAImage is not expected. "
+             << "(expected: " << num_planes
              << ", VAImage: " << va_image->image()->num_planes << ")";
     return nullptr;
   }
 
-  std::vector<int32_t> strides(num_planes, 0);
+  // All the planes are stored in the same buffer, VAImage.va_buffer.
+  std::vector<VideoFrameLayout::Plane> planes(num_planes);
   std::vector<uint8_t*> addrs(num_planes, nullptr);
   for (size_t i = 0; i < num_planes; i++) {
-    strides[i] = va_image->image()->pitches[i];
+    planes[i].stride = va_image->image()->pitches[i];
+    planes[i].offset = va_image->image()->offsets[i];
     addrs[i] = static_cast<uint8_t*>(va_image->va_buffer()->data()) +
                va_image->image()->offsets[i];
   }
 
-  auto video_frame = VideoFrame::WrapExternalYuvData(
-      layout.format(), layout.coded_size(), visible_rect, visible_rect.size(),
-      strides[0], strides[1], strides[2], addrs[0], addrs[1], addrs[2],
-      base::TimeDelta());
+  auto mapped_layout = VideoFrameLayout::CreateWithPlanes(
+      format, gfx::Size(va_image->image()->width, va_image->image()->height),
+      std::move(planes), {va_image->image()->data_size});
+  if (!mapped_layout) {
+    VLOGF(1) << "Failed to create VideoFrameLayout for VAImage";
+    return nullptr;
+  }
+  auto video_frame = VideoFrame::WrapExternalYuvDataWithLayout(
+      *mapped_layout, visible_rect, visible_rect.size(), addrs[0], addrs[1],
+      addrs[2], timestamp);
   if (!video_frame)
     return nullptr;
 
@@ -140,14 +149,8 @@
     return nullptr;
   }
 
-  auto layout =
-      VideoFrameLayout::Create(kConvertedFormat, video_frame->coded_size());
-  if (!layout) {
-    VLOGF(1) << "Failed to create VideoFrameLayout.";
-    return nullptr;
-  }
-  return CreateMappedVideoFrame(*layout, video_frame->visible_rect(),
-                                std::move(va_image));
+  return CreateMappedVideoFrame(kConvertedFormat, video_frame->visible_rect(),
+                                video_frame->timestamp(), std::move(va_image));
 }
 
 }  // namespace media
diff --git a/media/gpu/video_encode_accelerator_unittest.cc b/media/gpu/video_encode_accelerator_unittest.cc
index 6733fdf7..2f511b1 100644
--- a/media/gpu/video_encode_accelerator_unittest.cc
+++ b/media/gpu/video_encode_accelerator_unittest.cc
@@ -1929,28 +1929,38 @@
 
   size_t num_planes = VideoFrame::NumPlanes(test_stream_->pixel_format);
   CHECK_LE(num_planes, 3u);
+
   uint8_t* frame_data[3] = {};
-  size_t plane_stride[3] = {};
-  frame_data[0] =
-      reinterpret_cast<uint8_t*>(&test_stream_->aligned_in_file_data[0]) +
-      position;
-  for (size_t i = 1; i < num_planes; i++) {
-    frame_data[i] = frame_data[i - 1] + test_stream_->aligned_plane_size[i - 1];
-  }
+  std::vector<VideoFrameLayout::Plane> planes(num_planes);
+  size_t offset = position;
+  // All the planes are stored in the same buffer, aligned_in_file_data[0].
   for (size_t i = 0; i < num_planes; i++) {
-    plane_stride[i] = VideoFrame::RowBytes(i, test_stream_->pixel_format,
-                                           input_coded_size_.width());
+    frame_data[i] =
+        reinterpret_cast<uint8_t*>(&test_stream_->aligned_in_file_data[0]) +
+        offset;
+    planes[i].stride = VideoFrame::RowBytes(i, test_stream_->pixel_format,
+                                            input_coded_size_.width());
+    planes[i].offset = offset;
+    offset += test_stream_->aligned_plane_size[i];
   }
 
-  scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalYuvData(
-      test_stream_->pixel_format, input_coded_size_,
-      gfx::Rect(test_stream_->visible_size), test_stream_->visible_size,
-      plane_stride[0], plane_stride[1], plane_stride[2], frame_data[0],
-      frame_data[1], frame_data[2],
-      // Timestamp needs to avoid starting from 0.
-      base::TimeDelta().FromMilliseconds((next_input_id_ + 1) *
-                                         base::Time::kMillisecondsPerSecond /
-                                         current_framerate_));
+  auto layout = VideoFrameLayout::CreateWithPlanes(
+      test_stream_->pixel_format, input_coded_size_, std::move(planes),
+      {test_stream_->aligned_buffer_size});
+  if (!layout) {
+    LOG(ERROR) << "Failed to create VideoFrameLayout";
+    return nullptr;
+  }
+
+  scoped_refptr<VideoFrame> video_frame =
+      VideoFrame::WrapExternalYuvDataWithLayout(
+          *layout, gfx::Rect(test_stream_->visible_size),
+          test_stream_->visible_size, frame_data[0], frame_data[1],
+          frame_data[2],
+          // Timestamp needs to avoid starting from 0.
+          base::TimeDelta().FromMilliseconds(
+              (next_input_id_ + 1) * base::Time::kMillisecondsPerSecond /
+              current_framerate_));
   if (g_native_input) {
     video_frame = test::CreateDmabufFrameFromVideoFrame(std::move(video_frame));
   }
@@ -2491,22 +2501,33 @@
   CHECK_LE(num_planes, 3u);
   std::vector<char, AlignedAllocator<char, kPlatformBufferAlignment>>
       aligned_data[3];
+  std::vector<VideoFrameLayout::Plane> planes(num_planes);
+  std::vector<size_t> buffer_sizes(num_planes);
   uint8_t* frame_data[3] = {};
-  uint8_t plane_stride[3] = {};
+  // This VideoFrame is dummy. Each plane is stored in a separate buffer and
+  // each buffer size is the same as the plane size.
   for (size_t i = 0; i < num_planes; i++) {
-    aligned_data[i].resize(
-        VideoFrame::PlaneSize(pixel_format, i, input_coded_size).GetArea());
+    size_t plane_size =
+        VideoFrame::PlaneSize(pixel_format, i, input_coded_size).GetArea();
+    aligned_data[i].resize(plane_size);
     frame_data[i] = reinterpret_cast<uint8_t*>(aligned_data[i].data());
-    plane_stride[i] =
+    planes[i].stride =
         VideoFrame::RowBytes(i, pixel_format, input_coded_size.width());
+    planes[i].offset = 0;
+    buffer_sizes[i] = plane_size;
   }
 
-  scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalYuvData(
-      pixel_format, input_coded_size, gfx::Rect(input_coded_size),
-      input_coded_size, plane_stride[0], plane_stride[1], plane_stride[2],
-      frame_data[0], frame_data[1], frame_data[2],
-      base::TimeDelta().FromMilliseconds(base::Time::kMillisecondsPerSecond /
-                                         fps_));
+  auto layout = VideoFrameLayout::CreateWithPlanes(
+      pixel_format, input_coded_size, std::move(planes),
+      std::move(buffer_sizes));
+  ASSERT_TRUE(layout);
+
+  scoped_refptr<VideoFrame> video_frame =
+      VideoFrame::WrapExternalYuvDataWithLayout(
+          *layout, gfx::Rect(input_coded_size), input_coded_size, frame_data[0],
+          frame_data[1], frame_data[2],
+          base::TimeDelta().FromMilliseconds(
+              base::Time::kMillisecondsPerSecond / fps_));
 
   encoder_->Encode(video_frame, false);
 }
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 1a56b5b..3b57c2c4 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -36,6 +36,10 @@
 const base::Feature kScriptStreaming{"ScriptStreaming",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables user level memory pressure signal generation on Android.
+const base::Feature kUserLevelMemoryPressureSignal{
+    "UserLevelMemoryPressureSignal", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable FCP++ by experiment. See https://crbug.com/869924
 const base::Feature kFirstContentfulPaintPlusPlus{
     "FirstContentfulPaintPlusPlus", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 9f152855..ddd2d06 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -28,6 +28,7 @@
     "//content/*",
     "//third_party/blink/*",
     "//components/*",
+    "//services/*",
     "//storage/*",
     ":*",
   ]
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 4b675ff..db57004 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -19,6 +19,7 @@
 BLINK_COMMON_EXPORT extern const base::Feature
     kEnableGpuRasterizationViewportRestriction;
 BLINK_COMMON_EXPORT extern const base::Feature kScriptStreaming;
+BLINK_COMMON_EXPORT extern const base::Feature kUserLevelMemoryPressureSignal;
 BLINK_COMMON_EXPORT extern const base::Feature kFirstContentfulPaintPlusPlus;
 BLINK_COMMON_EXPORT extern const base::Feature kFreezePurgeMemoryAllPagesFrozen;
 BLINK_COMMON_EXPORT extern const base::Feature kImplicitRootScroller;
diff --git a/third_party/blink/public/common/screen_orientation/screen_orientation_lock_types.typemap b/third_party/blink/public/common/screen_orientation/screen_orientation_lock_types.typemap
index a262ea5..bc2dcf7f 100644
--- a/third_party/blink/public/common/screen_orientation/screen_orientation_lock_types.typemap
+++ b/third_party/blink/public/common/screen_orientation/screen_orientation_lock_types.typemap
@@ -6,3 +6,6 @@
 public_headers = [ "//third_party/blink/public/common/screen_orientation/web_screen_orientation_lock_type.h" ]
 traits_headers = [ "//third_party/blink/public/common/screen_orientation/web_screen_orientation_mojom_traits.h" ]
 type_mappings = [ "device.mojom.ScreenOrientationLockType=::blink::WebScreenOrientationLockType" ]
+deps = [
+  "//third_party/blink/public/common:headers",
+]
diff --git a/third_party/blink/public/web/web_security_policy.h b/third_party/blink/public/web/web_security_policy.h
index d4e69c7..0254232 100644
--- a/third_party/blink/public/web/web_security_policy.h
+++ b/third_party/blink/public/web/web_security_policy.h
@@ -90,14 +90,14 @@
       const WebURL& source_origin);
   BLINK_EXPORT static void ClearOriginAccessList();
 
-  // Support for whitelisting origins or hostname patterns to treat them as
-  // trustworthy. This method does not do any canonicalization; the caller is
-  // responsible for canonicalizing them before calling this.
-  BLINK_EXPORT static void AddOriginTrustworthyWhiteList(const WebString&);
+  // Adds an origin or hostname pattern that is always considered trustworthy.
+  // This method does not perform canonicalization; the caller is responsible
+  // for canonicalizing the input.
+  BLINK_EXPORT static void AddOriginToTrustworthySafelist(const WebString&);
 
-  // Support for whitelisting schemes as bypassing secure context checks.
-  BLINK_EXPORT static void AddSchemeToBypassSecureContextWhitelist(
-      const WebString&);
+  // Add a scheme that is always considered a secure context. The caller is
+  // responsible for canonicalizing the input.
+  BLINK_EXPORT static void AddSchemeToSecureContextSafelist(const WebString&);
 
   // Returns the referrer modified according to the referrer policy for a
   // navigation to a given URL. If the referrer returned is empty, the
diff --git a/third_party/blink/renderer/bindings/IDLExtendedAttributes.md b/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
index 644c80f..f29c7d3 100644
--- a/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
+++ b/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
@@ -112,20 +112,18 @@
 interface must have `[ImplementedAs]` extended attribute to specify a static-only C++ class.
 This is stored internally via `[PartialInterfaceImplementedAs]` (see below).
 
-### implements
+### interface mixins
 
-Extended attributes on members of an implemented interface work as normal. However, only the following 5 extended attributes can be used on the implemented interface itself; otherwise extended attributes should appear on the main (implementing) interface definition:
+Extended attributes on members of an interface mixin work as normal. However, only the following 5 extended attributes can be used on the interface mixin itself; otherwise extended attributes should appear on the main (including) interface definition:
 
-* `[LegacyTreatAsPartialInterface]` is part of an ongoing change, as implemented interfaces used to be treated internally as partial interfaces.
+* `[LegacyTreatAsPartialInterface]` is part of an ongoing change, as interface mixins used to be treated internally as partial interfaces.
 
-* `[ImplementedAs]` is only necessary for these legacy files: otherwise the class (C++) implementing (IDL) implemented interfaces does not need to be specified, as this is handled in Blink C++.
+* `[ImplementedAs]` is only necessary for these legacy files: otherwise the class (C++) implementing (IDL) interface mixins does not need to be specified, as this is handled in Blink C++.
 
 * `[OriginTrialEnabled]` behaves as for partial interfaces.
 
 * `[RuntimeEnabled]` behaves as for partial interfaces.
 
-* `[NoInterfaceObject]` is _always_ specified on implemented interfaces.
-
 ### Inheritance
 
 Extended attributes are generally not inherited: only extended attributes on the interface itself are consulted. However, there are a handful of extended attributes that are inherited (applying them to an ancestor interface applies them to the descendants). These are extended attributes that affect memory management, and currently consists of `[ActiveScriptWrappable]`; the up-to-date list is [compute_dependencies.INHERITED_EXTENDED_ATTRIBUTES](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/blink/renderer/bindings/scripts/compute_dependencies.py&q=INHERITED_EXTENDED_ATTRIBUTES).
@@ -1493,7 +1491,7 @@
 
 ### [LegacyTreatAsPartialInterface] _(i)_
 
-Summary: `[LegacyTreatAsPartialInterface]` on an interface that is the target of an `implements` statement means that the interface is treated as a partial interface, meaning members are accessed via static member functions in a separate class, rather than as instance methods on the instance object `*impl` or class methods on the C++ class implementing the (main) interface. This is legacy from original implementation of `implements`, and is being removed ([Bug 360435](https://crbug.com/360435), nbarth@).
+Summary: `[LegacyTreatAsPartialInterface]` on an interface mixin means that the mixin is treated as a partial interface, meaning members are accessed via static member functions in a separate class, rather than as instance methods on the instance object `*impl` or class methods on the C++ class implementing the (main) interface. This is legacy from original implementation of mixins, and is being removed ([Bug 360435](https://crbug.com/360435), nbarth@).
 
 
 ### [CachedAccessor] _(a)_
diff --git a/third_party/blink/renderer/controller/BUILD.gn b/third_party/blink/renderer/controller/BUILD.gn
index a52bb9d..5a2a625 100644
--- a/third_party/blink/renderer/controller/BUILD.gn
+++ b/third_party/blink/renderer/controller/BUILD.gn
@@ -56,6 +56,8 @@
       "memory_usage_monitor_android.h",
       "oom_intervention_impl.cc",
       "oom_intervention_impl.h",
+      "user_level_memory_pressure_signal_generator.cc",
+      "user_level_memory_pressure_signal_generator.h",
     ]
   }
   if (is_mac) {
@@ -153,6 +155,7 @@
       "memory_usage_monitor_android_test.cc",
       "memory_usage_monitor_test.cc",
       "oom_intervention_impl_test.cc",
+      "user_level_memory_pressure_signal_generator_test.cc",
     ]
   }
 
diff --git a/third_party/blink/renderer/controller/blink_initializer.cc b/third_party/blink/renderer/controller/blink_initializer.cc
index 78dcd89..747405c 100644
--- a/third_party/blink/renderer/controller/blink_initializer.cc
+++ b/third_party/blink/renderer/controller/blink_initializer.cc
@@ -59,6 +59,7 @@
 #if defined(OS_ANDROID)
 #include "third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h"
 #include "third_party/blink/renderer/controller/oom_intervention_impl.h"
+#include "third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h"
 #endif
 
 namespace blink {
@@ -133,6 +134,9 @@
     MemoryAblationExperiment::MaybeStartForRenderer(task_runner);
 
 #if defined(OS_ANDROID)
+  // Initialize UserLevelMemoryPressureSignalGenerator so it starts monitoring.
+  UserLevelMemoryPressureSignalGenerator::Instance();
+
   // Initialize CrashMemoryMetricsReporterImpl in order to assure that memory
   // allocation does not happen in OnOOMCallback.
   CrashMemoryMetricsReporterImpl::Instance();
diff --git a/third_party/blink/renderer/controller/memory_usage_monitor.h b/third_party/blink/renderer/controller/memory_usage_monitor.h
index 7b8e120..68ebf79 100644
--- a/third_party/blink/renderer/controller/memory_usage_monitor.h
+++ b/third_party/blink/renderer/controller/memory_usage_monitor.h
@@ -29,6 +29,7 @@
 
  public:
   static MemoryUsageMonitor& Instance();
+  static void SetInstanceForTesting(MemoryUsageMonitor*);
 
   class Observer : public base::CheckedObserver {
    public:
diff --git a/third_party/blink/renderer/controller/memory_usage_monitor_android.cc b/third_party/blink/renderer/controller/memory_usage_monitor_android.cc
index beb9b1b..f82ce3c 100644
--- a/third_party/blink/renderer/controller/memory_usage_monitor_android.cc
+++ b/third_party/blink/renderer/controller/memory_usage_monitor_android.cc
@@ -23,12 +23,19 @@
   return true;
 }
 
+static MemoryUsageMonitor* g_instance_for_testing = nullptr;
+
 }  // namespace
 
 // static
 MemoryUsageMonitor& MemoryUsageMonitor::Instance() {
   DEFINE_STATIC_LOCAL(MemoryUsageMonitorAndroid, monitor, ());
-  return monitor;
+  return g_instance_for_testing ? *g_instance_for_testing : monitor;
+}
+
+// static
+void MemoryUsageMonitor::SetInstanceForTesting(MemoryUsageMonitor* instance) {
+  g_instance_for_testing = instance;
 }
 
 // Since the measurement is done every second in background, optimizations are
diff --git a/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.cc b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.cc
new file mode 100644
index 0000000..b5c8df3a
--- /dev/null
+++ b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.cc
@@ -0,0 +1,139 @@
+// Copyright 2019 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 "third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h"
+
+#include <limits>
+#include "base/memory/memory_pressure_listener.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/system/sys_info.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
+#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
+#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
+
+namespace blink {
+
+namespace {
+
+constexpr double kDefaultMemoryThresholdMB =
+    std::numeric_limits<double>::infinity();
+
+constexpr base::FeatureParam<double> k512MBDeviceMemoryThresholdParam{
+    &blink::features::kUserLevelMemoryPressureSignal,
+    "param_512mb_device_memory_threshold_mb", kDefaultMemoryThresholdMB};
+
+constexpr base::FeatureParam<double> k1GBDeviceMemoryThresholdParam{
+    &blink::features::kUserLevelMemoryPressureSignal,
+    "param_1gb_device_memory_threshold_mb", kDefaultMemoryThresholdMB};
+
+constexpr base::FeatureParam<double> k2GBDeviceMemoryThresholdParam{
+    &blink::features::kUserLevelMemoryPressureSignal,
+    "param_2gb_device_memory_threshold_mb", kDefaultMemoryThresholdMB};
+
+constexpr base::FeatureParam<double> k3GBDeviceMemoryThresholdParam{
+    &blink::features::kUserLevelMemoryPressureSignal,
+    "param_3gb_device_memory_threshold_mb", kDefaultMemoryThresholdMB};
+
+constexpr base::FeatureParam<double> k4GBDeviceMemoryThresholdParam{
+    &blink::features::kUserLevelMemoryPressureSignal,
+    "param_4gb_device_memory_threshold_mb", kDefaultMemoryThresholdMB};
+
+// Minimum time interval between generated memory pressure signals.
+constexpr double kDefaultMinimumIntervalSeconds = 10 * 60;
+
+constexpr base::FeatureParam<double> kMinimumIntervalSeconds{
+    &blink::features::kUserLevelMemoryPressureSignal, "minimum_interval_s",
+    kDefaultMinimumIntervalSeconds};
+
+}  // namespace
+
+// static
+UserLevelMemoryPressureSignalGenerator&
+UserLevelMemoryPressureSignalGenerator::Instance() {
+  DEFINE_STATIC_LOCAL(UserLevelMemoryPressureSignalGenerator, generator, ());
+  return generator;
+}
+
+UserLevelMemoryPressureSignalGenerator::UserLevelMemoryPressureSignalGenerator()
+    : delayed_report_timer_(
+          Thread::MainThread()->GetTaskRunner(),
+          this,
+          &UserLevelMemoryPressureSignalGenerator::OnTimerFired) {
+  int64_t physical_memory = base::SysInfo::AmountOfPhysicalMemory();
+  if (physical_memory > 3.1 * 1024 * 1024 * 1024)
+    memory_threshold_mb_ = k4GBDeviceMemoryThresholdParam.Get();
+  else if (physical_memory > 2.1 * 1024 * 1024 * 1024)
+    memory_threshold_mb_ = k3GBDeviceMemoryThresholdParam.Get();
+  else if (physical_memory > 1.1 * 1024 * 1024 * 1024)
+    memory_threshold_mb_ = k2GBDeviceMemoryThresholdParam.Get();
+  else if (physical_memory > 600 * 1024 * 1024)
+    memory_threshold_mb_ = k1GBDeviceMemoryThresholdParam.Get();
+  else
+    memory_threshold_mb_ = k512MBDeviceMemoryThresholdParam.Get();
+
+  minimum_interval_ =
+      WTF::TimeDelta::FromSeconds(kMinimumIntervalSeconds.Get());
+
+  // Can be disabled for certain device classes by setting the field param to an
+  // empty string.
+  bool enabled = base::FeatureList::IsEnabled(
+                     blink::features::kUserLevelMemoryPressureSignal) &&
+                 !std::isinf(memory_threshold_mb_);
+  if (enabled) {
+    monitoring_ = true;
+    MemoryUsageMonitor::Instance().AddObserver(this);
+    ThreadScheduler::Current()->AddRAILModeObserver(this);
+  }
+}
+
+UserLevelMemoryPressureSignalGenerator::
+    ~UserLevelMemoryPressureSignalGenerator() {
+  MemoryUsageMonitor::Instance().RemoveObserver(this);
+  ThreadScheduler::Current()->RemoveRAILModeObserver(this);
+}
+
+void UserLevelMemoryPressureSignalGenerator::OnRAILModeChanged(
+    RAILMode rail_mode) {
+  is_loading_ = rail_mode == RAILMode::kLoad;
+}
+
+void UserLevelMemoryPressureSignalGenerator::OnMemoryPing(MemoryUsage usage) {
+  // Disabled during loading as we don't want to purge caches that has just been
+  // created.
+  if (is_loading_)
+    return;
+  if (usage.private_footprint_bytes / 1024 / 1024 < memory_threshold_mb_)
+    return;
+  base::TimeDelta elapsed = WTF::CurrentTimeTicks() - last_generated_;
+  if (elapsed >= WTF::TimeDelta::FromSeconds(kMinimumIntervalSeconds.Get()))
+    Generate(usage);
+}
+
+void UserLevelMemoryPressureSignalGenerator::Generate(MemoryUsage usage) {
+  UMA_HISTOGRAM_MEMORY_LARGE_MB(
+      "Memory.Experimental.UserLevelMemoryPressureSignal."
+      "RendererPrivateMemoryFootprintBefore",
+      base::saturated_cast<base::Histogram::Sample>(
+          usage.private_footprint_bytes / 1024 / 1024));
+
+  base::MemoryPressureListener::NotifyMemoryPressure(
+      base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
+  last_generated_ = WTF::CurrentTimeTicks();
+
+  delayed_report_timer_.StartOneShot(TimeDelta::FromSeconds(10), FROM_HERE);
+}
+
+void UserLevelMemoryPressureSignalGenerator::OnTimerFired(TimerBase*) {
+  MemoryUsage usage = MemoryUsageMonitor::Instance().GetCurrentMemoryUsage();
+  UMA_HISTOGRAM_MEMORY_LARGE_MB(
+      "Memory.Experimental.UserLevelMemoryPressureSignal."
+      "RendererPrivateMemoryFootprintAfter",
+      base::saturated_cast<base::Histogram::Sample>(
+          usage.private_footprint_bytes / 1024 / 1024));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h
new file mode 100644
index 0000000..d79d629
--- /dev/null
+++ b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h
@@ -0,0 +1,60 @@
+// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_CONTROLLER_USER_LEVEL_MEMORY_PRESSURE_SIGNAL_GENERATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CONTROLLER_USER_LEVEL_MEMORY_PRESSURE_SIGNAL_GENERATOR_H_
+
+#include "third_party/blink/renderer/controller/controller_export.h"
+#include "third_party/blink/renderer/controller/memory_usage_monitor.h"
+#include "third_party/blink/renderer/platform/scheduler/public/rail_mode_observer.h"
+#include "third_party/blink/renderer/platform/timer.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+namespace user_level_memory_pressure_signal_generator_test {
+class MockUserLevelMemoryPressureSignalGenerator;
+}  // namespace user_level_memory_pressure_signal_generator_test
+
+// Generates extra memory pressure signals (on top of the OS generated ones)
+// when the memory usage goes over a threshold.
+class CONTROLLER_EXPORT UserLevelMemoryPressureSignalGenerator
+    : public RAILModeObserver,
+      public MemoryUsageMonitor::Observer {
+  USING_FAST_MALLOC(UserLevelMemoryPressureSignalGenerator);
+
+ public:
+  // Returns the shared instance.
+  static UserLevelMemoryPressureSignalGenerator& Instance();
+
+  UserLevelMemoryPressureSignalGenerator();
+  ~UserLevelMemoryPressureSignalGenerator() override;
+
+ private:
+  friend class user_level_memory_pressure_signal_generator_test::
+      MockUserLevelMemoryPressureSignalGenerator;
+
+  // This is only virtual to override in tests.
+  virtual void Generate(MemoryUsage);
+
+  // Called by delayed_report_timer_ to report metrics.
+  void OnTimerFired(TimerBase*);
+
+  // RAILModeObserver:
+  void OnRAILModeChanged(RAILMode rail_mode) override;
+
+  // MemoryUsageMonitor::Observer:
+  void OnMemoryPing(MemoryUsage) override;
+
+  bool monitoring_ = false;
+  bool is_loading_ = false;
+  WTF::TimeTicks last_generated_;
+  double memory_threshold_mb_;
+  WTF::TimeDelta minimum_interval_;
+  TaskRunnerTimer<UserLevelMemoryPressureSignalGenerator> delayed_report_timer_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CONTROLLER_USER_LEVEL_MEMORY_PRESSURE_SIGNAL_GENERATOR_H_
diff --git a/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc
new file mode 100644
index 0000000..2321c5b
--- /dev/null
+++ b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc
@@ -0,0 +1,173 @@
+// Copyright 2019 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 "third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h"
+
+namespace blink {
+namespace user_level_memory_pressure_signal_generator_test {
+
+using testing::_;
+
+// Mock that allows setting mock memory usage.
+class MockMemoryUsageMonitor : public MemoryUsageMonitor {
+ public:
+  MockMemoryUsageMonitor() = default;
+  ~MockMemoryUsageMonitor() override = default;
+
+  MemoryUsage GetCurrentMemoryUsage() override { return mock_memory_usage_; }
+
+  // MemoryUsageMonitor will report the current memory usage as this value.
+  void SetMockMemoryUsage(MemoryUsage usage) { mock_memory_usage_ = usage; }
+
+ private:
+  MemoryUsage mock_memory_usage_;
+};
+
+class MockUserLevelMemoryPressureSignalGenerator
+    : public UserLevelMemoryPressureSignalGenerator {
+ public:
+  MockUserLevelMemoryPressureSignalGenerator() {
+    ON_CALL(*this, Generate(_))
+        .WillByDefault(testing::Invoke(
+            this, &MockUserLevelMemoryPressureSignalGenerator::RealGenerate));
+  }
+  ~MockUserLevelMemoryPressureSignalGenerator() override = default;
+
+  MOCK_METHOD1(Generate, void(MemoryUsage));
+
+  void RealGenerate(MemoryUsage usage) {
+    UserLevelMemoryPressureSignalGenerator::Generate(usage);
+  }
+
+  using UserLevelMemoryPressureSignalGenerator::OnRAILModeChanged;
+};
+
+class ScopedMockMemoryUsageMonitor {
+ public:
+  ScopedMockMemoryUsageMonitor(MemoryUsageMonitor* monitor) {
+    MemoryUsageMonitor::SetInstanceForTesting(monitor);
+  }
+  ~ScopedMockMemoryUsageMonitor() {
+    MemoryUsageMonitor::SetInstanceForTesting(nullptr);
+  }
+};
+
+class UserLevelMemoryPressureSignalGeneratorTest : public testing::Test {
+ public:
+  UserLevelMemoryPressureSignalGeneratorTest() = default;
+
+  void SetUp() override {
+    std::map<std::string, std::string> feature_parameters;
+    feature_parameters["param_512mb_device_memory_threshold_mb"] = "1024.0";
+    feature_parameters["param_1gb_device_memory_threshold_mb"] = "1024.0";
+    feature_parameters["param_2gb_device_memory_threshold_mb"] = "1024.0";
+    feature_parameters["param_3gb_device_memory_threshold_mb"] = "1024.0";
+    feature_parameters["param_4gb_device_memory_threshold_mb"] = "1024.0";
+    feature_parameters["minimum_interval_s"] = "600.0";
+
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kUserLevelMemoryPressureSignal, feature_parameters);
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+constexpr double kMemoryThresholdBytes = 1024 * 1024 * 1024;
+
+TEST_F(UserLevelMemoryPressureSignalGeneratorTest, GeneratesWhenOverThreshold) {
+  {
+    WTF::ScopedMockClock clock;
+    std::unique_ptr<MockMemoryUsageMonitor> mock_memory_usage_monitor =
+        std::make_unique<MockMemoryUsageMonitor>();
+    ScopedMockMemoryUsageMonitor mock_memory_usage_scope(
+        mock_memory_usage_monitor.get());
+    MockUserLevelMemoryPressureSignalGenerator generator;
+    {
+      EXPECT_CALL(generator, Generate(_)).Times(0);
+      MemoryUsage usage;
+      usage.v8_bytes = 0;
+      usage.blink_gc_bytes = 0;
+      usage.partition_alloc_bytes = 0;
+      usage.private_footprint_bytes = kMemoryThresholdBytes - 1024 * 1024;
+      usage.swap_bytes = 0;
+      usage.vm_size_bytes = 0;
+      mock_memory_usage_monitor->SetMockMemoryUsage(usage);
+      clock.Advance(TimeDelta::FromSeconds(1));
+      test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+    }
+    {
+      EXPECT_CALL(generator, Generate(_)).Times(1);
+      MemoryUsage usage;
+      usage.v8_bytes = 0;
+      usage.blink_gc_bytes = 0;
+      usage.partition_alloc_bytes = 0;
+      usage.private_footprint_bytes = kMemoryThresholdBytes + 1024 * 1024;
+      usage.swap_bytes = 0;
+      usage.vm_size_bytes = 0;
+      mock_memory_usage_monitor->SetMockMemoryUsage(usage);
+      clock.Advance(TimeDelta::FromMinutes(10));
+      test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+    }
+  }
+}
+
+TEST_F(UserLevelMemoryPressureSignalGeneratorTest, GenerationPauses) {
+  {
+    WTF::ScopedMockClock clock;
+    std::unique_ptr<MockMemoryUsageMonitor> mock_memory_usage_monitor =
+        std::make_unique<MockMemoryUsageMonitor>();
+    ScopedMockMemoryUsageMonitor mock_memory_usage_scope(
+        mock_memory_usage_monitor.get());
+    MockUserLevelMemoryPressureSignalGenerator generator;
+    {
+      MemoryUsage usage;
+      usage.v8_bytes = 0;
+      usage.blink_gc_bytes = 0;
+      usage.partition_alloc_bytes = 0;
+      usage.private_footprint_bytes = kMemoryThresholdBytes + 1024 * 1024;
+      usage.swap_bytes = 0;
+      usage.vm_size_bytes = 0;
+      mock_memory_usage_monitor->SetMockMemoryUsage(usage);
+      clock.Advance(TimeDelta::FromMinutes(10));
+      // Generated
+      {
+        EXPECT_CALL(generator, Generate(_)).Times(1);
+        test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+      }
+
+      clock.Advance(TimeDelta::FromMinutes(1));
+      // Not generated because too soon
+      {
+        EXPECT_CALL(generator, Generate(_)).Times(0);
+        test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+      }
+
+      clock.Advance(TimeDelta::FromMinutes(10));
+      generator.OnRAILModeChanged(RAILMode::kLoad);
+      // Not generated because loading
+      {
+        EXPECT_CALL(generator, Generate(_)).Times(0);
+        test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+      }
+
+      generator.OnRAILModeChanged(RAILMode::kAnimation);
+      // Generated
+      {
+        EXPECT_CALL(generator, Generate(_)).Times(1);
+        test::RunDelayedTasks(TimeDelta::FromSeconds(1));
+      }
+    }
+  }
+}
+
+}  // namespace user_level_memory_pressure_signal_generator_test
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_style_sheet.cc b/third_party/blink/renderer/core/css/css_style_sheet.cc
index e011bff..1734eda 100644
--- a/third_party/blink/renderer/core/css/css_style_sheet.cc
+++ b/third_party/blink/renderer/core/css/css_style_sheet.cc
@@ -42,6 +42,7 @@
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/svg/svg_style_element.h"
+#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
@@ -413,11 +414,10 @@
          child_rule_cssom_wrappers_.size() == contents_->RuleCount());
 
   if (index >= length()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kIndexSizeError,
-        "The index provided (" + String::Number(index) +
-            ") is larger than the maximum index (" +
-            String::Number(length() - 1) + ").");
+    exception_state.ThrowRangeError(
+        ExceptionMessages::IndexOutsideRange<unsigned>(
+            "index", index, 0, ExceptionMessages::kInclusiveBound, length(),
+            ExceptionMessages::kExclusiveBound));
     return;
   }
   RuleMutationScope mutation_scope(this);
diff --git a/third_party/blink/renderer/core/dom/events/event_target.cc b/third_party/blink/renderer/core/dom/events/event_target.cc
index a9049a6..491e59a 100644
--- a/third_party/blink/renderer/core/dom/events/event_target.cc
+++ b/third_party/blink/renderer/core/dom/events/event_target.cc
@@ -453,6 +453,30 @@
         UseCounter::Count(*document, WebFeature::kSlotChangeEventAddListener);
     }
   }
+
+  // TODO(altimin): Ideally we would also support tracking unregistration of
+  // event listeners, but we don't do this for performance reasons.
+  base::Optional<SchedulingPolicy::Feature> feature_for_scheduler;
+  if (event_type == event_type_names::kPageshow) {
+    feature_for_scheduler = SchedulingPolicy::Feature::kPageShowEventListener;
+  } else if (event_type == event_type_names::kPagehide) {
+    feature_for_scheduler = SchedulingPolicy::Feature::kPageHideEventListener;
+  } else if (event_type == event_type_names::kBeforeunload) {
+    feature_for_scheduler =
+        SchedulingPolicy::Feature::kBeforeUnloadEventListener;
+  } else if (event_type == event_type_names::kUnload) {
+    feature_for_scheduler = SchedulingPolicy::Feature::kUnloadEventListener;
+  } else if (event_type == event_type_names::kFreeze) {
+    feature_for_scheduler = SchedulingPolicy::Feature::kFreezeEventListener;
+  } else if (event_type == event_type_names::kResume) {
+    feature_for_scheduler = SchedulingPolicy::Feature::kResumeEventListener;
+  }
+  if (feature_for_scheduler) {
+    GetExecutionContext()->GetScheduler()->RegisterStickyFeature(
+        feature_for_scheduler.value(),
+        {SchedulingPolicy::DisableBackForwardCache()});
+  }
+
   if (event_util::IsDOMMutationEventType(event_type)) {
     if (ExecutionContext* context = GetExecutionContext()) {
       String message_text = String::Format(
diff --git a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
index 1957396..697643174 100644
--- a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
+++ b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
@@ -969,8 +969,8 @@
 
   GetDocument().UpdateStyleAndLayout();
 
-  auto* block = To<LayoutBlockFlow>(container->GetLayoutObject());
-  if (!container->GetLayoutObject() || !block)
+  auto* block = DynamicTo<LayoutBlockFlow>(container->GetLayoutObject());
+  if (!block)
     return nullptr;
 
   // append the placeholder to make sure it follows
diff --git a/third_party/blink/renderer/core/exported/web_security_policy.cc b/third_party/blink/renderer/core/exported/web_security_policy.cc
index 88d753e..94f1c68 100644
--- a/third_party/blink/renderer/core/exported/web_security_policy.cc
+++ b/third_party/blink/renderer/core/exported/web_security_policy.cc
@@ -99,11 +99,12 @@
   SecurityPolicy::ClearOriginAccessList();
 }
 
-void WebSecurityPolicy::AddOriginTrustworthyWhiteList(const WebString& origin) {
-  SecurityPolicy::AddOriginTrustworthyWhiteList(origin);
+void WebSecurityPolicy::AddOriginToTrustworthySafelist(
+    const WebString& origin) {
+  SecurityPolicy::AddOriginToTrustworthySafelist(origin);
 }
 
-void WebSecurityPolicy::AddSchemeToBypassSecureContextWhitelist(
+void WebSecurityPolicy::AddSchemeToSecureContextSafelist(
     const WebString& scheme) {
   SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck(scheme);
 }
diff --git a/third_party/blink/renderer/core/fileapi/blob.cc b/third_party/blink/renderer/core/fileapi/blob.cc
index 513c4c9..a3ef226 100644
--- a/third_party/blink/renderer/core/fileapi/blob.cc
+++ b/third_party/blink/renderer/core/fileapi/blob.cc
@@ -84,8 +84,7 @@
 Blob* Blob::Create(
     ExecutionContext* context,
     const HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString>& blob_parts,
-    const BlobPropertyBag* options,
-    ExceptionState& exception_state) {
+    const BlobPropertyBag* options) {
   DCHECK(options->hasType());
 
   DCHECK(options->hasEndings());
diff --git a/third_party/blink/renderer/core/fileapi/blob.h b/third_party/blink/renderer/core/fileapi/blob.h
index 32f5e44..973296a 100644
--- a/third_party/blink/renderer/core/fileapi/blob.h
+++ b/third_party/blink/renderer/core/fileapi/blob.h
@@ -55,15 +55,14 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static Blob* Create(ExecutionContext*, ExceptionState&) {
+  static Blob* Create(ExecutionContext*) {
     return MakeGarbageCollected<Blob>(BlobDataHandle::Create());
   }
 
   static Blob* Create(
       ExecutionContext*,
       const HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString>&,
-      const BlobPropertyBag*,
-      ExceptionState&);
+      const BlobPropertyBag*);
 
   static Blob* Create(scoped_refptr<BlobDataHandle> blob_data_handle) {
     return MakeGarbageCollected<Blob>(std::move(blob_data_handle));
diff --git a/third_party/blink/renderer/core/fileapi/blob.idl b/third_party/blink/renderer/core/fileapi/blob.idl
index 27393e6..dde6334 100644
--- a/third_party/blink/renderer/core/fileapi/blob.idl
+++ b/third_party/blink/renderer/core/fileapi/blob.idl
@@ -34,7 +34,6 @@
 [
     Constructor(optional sequence<BlobPart> blobParts, optional BlobPropertyBag options),
     ConstructorCallWith=ExecutionContext,
-    RaisesException=Constructor,
     Exposed=(Window,Worker),
     Serializable
 ] interface Blob {
diff --git a/third_party/blink/renderer/core/fileapi/file.cc b/third_party/blink/renderer/core/fileapi/file.cc
index 3003a2a..bf49b29 100644
--- a/third_party/blink/renderer/core/fileapi/file.cc
+++ b/third_party/blink/renderer/core/fileapi/file.cc
@@ -127,8 +127,7 @@
     ExecutionContext* context,
     const HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString>& file_bits,
     const String& file_name,
-    const FilePropertyBag* options,
-    ExceptionState& exception_state) {
+    const FilePropertyBag* options) {
   DCHECK(options->hasType());
 
   double last_modified;
diff --git a/third_party/blink/renderer/core/fileapi/file.h b/third_party/blink/renderer/core/fileapi/file.h
index 3abff1a..05e05fb 100644
--- a/third_party/blink/renderer/core/fileapi/file.h
+++ b/third_party/blink/renderer/core/fileapi/file.h
@@ -64,8 +64,7 @@
       ExecutionContext*,
       const HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString>&,
       const String& file_name,
-      const FilePropertyBag*,
-      ExceptionState&);
+      const FilePropertyBag*);
 
   static File* Create(const String& path,
                       ContentTypeLookupPolicy policy = kWellKnownContentTypes) {
diff --git a/third_party/blink/renderer/core/fileapi/file.idl b/third_party/blink/renderer/core/fileapi/file.idl
index 940f8e6..41b8183 100644
--- a/third_party/blink/renderer/core/fileapi/file.idl
+++ b/third_party/blink/renderer/core/fileapi/file.idl
@@ -28,7 +28,6 @@
 [
     Constructor(sequence<BlobPart> fileBits, USVString fileName, optional FilePropertyBag options),
     ConstructorCallWith=ExecutionContext,
-    RaisesException=Constructor,
     Exposed=(Window,Worker),
     Serializable
 ] interface File : Blob {
diff --git a/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc b/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
index 92f63d0..0d6caeb 100644
--- a/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
+++ b/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
@@ -148,4 +148,82 @@
               testing::UnorderedElementsAre());
 }
 
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_PageShow) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"pageshow\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kPageShowEventListener));
+}
+
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_PageHide) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"pagehide\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kPageHideEventListener));
+}
+
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_BeforeUnload) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"beforeunload\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kBeforeUnloadEventListener));
+}
+
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_Unload) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"unload\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kUnloadEventListener));
+}
+
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_Freeze) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"freeze\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kFreezeEventListener));
+}
+
+TEST_F(SchedulingAffectingFeaturesTest, EventListener_Resume) {
+  SimRequest main_resource("https://foo.com/", "text/html");
+  LoadURL("https://foo.com/");
+  main_resource.Complete(
+      "(<script>"
+      " window.addEventListener(\"resume\", () => {}); "
+      "</script>)");
+
+  EXPECT_THAT(PageScheduler()->GetActiveFeaturesOptingOutFromBackForwardCache(),
+              testing::UnorderedElementsAre(
+                  SchedulingPolicy::Feature::kResumeEventListener));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webshare/navigator_share_test.cc b/third_party/blink/renderer/modules/webshare/navigator_share_test.cc
index 26b7544a..82c4466 100644
--- a/third_party/blink/renderer/modules/webshare/navigator_share_test.cc
+++ b/third_party/blink/renderer/modules/webshare/navigator_share_test.cc
@@ -150,10 +150,8 @@
   file_property_bag.setType(content_type);
 
   HeapVector<Member<File>> files;
-  DummyExceptionStateForTesting exception_state;
   files.push_back(File::Create(ExecutionContext::From(GetScriptState()),
-                               blob_parts, file_name, &file_property_bag,
-                               exception_state));
+                               blob_parts, file_name, &file_property_bag));
 
   ShareData share_data;
   share_data.setFiles(files);
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
index 5734ef8..a52a4c8 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
@@ -17,6 +17,12 @@
     case Feature::kMainResourceHasCacheControlNoCache:
     case Feature::kSubresourceHasCacheControlNoStore:
     case Feature::kSubresourceHasCacheControlNoCache:
+    case Feature::kPageShowEventListener:
+    case Feature::kPageHideEventListener:
+    case Feature::kBeforeUnloadEventListener:
+    case Feature::kUnloadEventListener:
+    case Feature::kFreezeEventListener:
+    case Feature::kResumeEventListener:
       return true;
     case Feature::kCount:
       NOTREACHED();
diff --git a/third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h b/third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h
index 7f96cca..0ac594a0 100644
--- a/third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h
+++ b/third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h
@@ -24,7 +24,14 @@
     kSubresourceHasCacheControlNoCache = 4,
     kSubresourceHasCacheControlNoStore = 5,
 
-    kCount = 6
+    kPageShowEventListener = 6,
+    kPageHideEventListener = 7,
+    kBeforeUnloadEventListener = 8,
+    kUnloadEventListener = 9,
+    kFreezeEventListener = 10,
+    kResumeEventListener = 11,
+
+    kCount = 12
   };
 
   // Sticky features can't be unregistered and remain active for the rest
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin.cc b/third_party/blink/renderer/platform/weborigin/security_origin.cc
index a759cad..52a04300 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin.cc
@@ -292,7 +292,7 @@
                                     ExtractInnerURL(url).Protocol()))
     return true;
 
-  if (SecurityPolicy::IsUrlWhiteListedTrustworthy(url))
+  if (SecurityPolicy::IsUrlTrustworthySafelisted(url))
     return true;
 
   return false;
@@ -466,7 +466,7 @@
     return true;
   }
 
-  if (SecurityPolicy::IsOriginWhiteListedTrustworthy(*this))
+  if (SecurityPolicy::IsOriginTrustworthySafelisted(*this))
     return true;
 
   return false;
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
index ec75ad8..2308c10 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
@@ -204,7 +204,7 @@
   for (const char* test : urls) {
     KURL url(test);
     EXPECT_FALSE(SecurityOrigin::IsSecure(url));
-    SecurityPolicy::AddOriginTrustworthyWhiteList(
+    SecurityPolicy::AddOriginToTrustworthySafelist(
         SecurityOrigin::CreateFromString(url)->ToRawString());
     EXPECT_TRUE(SecurityOrigin::IsSecure(url));
   }
@@ -213,7 +213,7 @@
 TEST_F(SecurityOriginTest, IsSecureViaTrustworthyHostnamePattern) {
   KURL url("http://bar.foo.com");
   EXPECT_FALSE(SecurityOrigin::IsSecure(url));
-  SecurityPolicy::AddOriginTrustworthyWhiteList("*.foo.com");
+  SecurityPolicy::AddOriginToTrustworthySafelist("*.foo.com");
   EXPECT_TRUE(SecurityOrigin::IsSecure(url));
 }
 
@@ -221,7 +221,7 @@
 TEST_F(SecurityOriginTest, IsSecureViaTrustworthyHostnamePatternEmptyHostname) {
   KURL url("file://foo");
   EXPECT_FALSE(SecurityOrigin::IsSecure(url));
-  SecurityPolicy::AddOriginTrustworthyWhiteList("*.foo.com");
+  SecurityPolicy::AddOriginToTrustworthySafelist("*.foo.com");
   EXPECT_FALSE(SecurityOrigin::IsSecure(url));
 }
 
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.cc b/third_party/blink/renderer/platform/weborigin/security_policy.cc
index d821a98..d2b74b7e 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.cc
@@ -63,13 +63,13 @@
 
 using OriginSet = HashSet<String>;
 
-static OriginSet& TrustworthyOriginSet() {
-  DEFINE_STATIC_LOCAL(OriginSet, trustworthy_origin_set, ());
-  return trustworthy_origin_set;
+static OriginSet& TrustworthyOriginSafelist() {
+  DEFINE_STATIC_LOCAL(OriginSet, safelist, ());
+  return safelist;
 }
 
 void SecurityPolicy::Init() {
-  TrustworthyOriginSet();
+  TrustworthyOriginSafelist();
 }
 
 bool SecurityPolicy::ShouldHideReferrer(const KURL& url, const KURL& referrer) {
@@ -181,31 +181,32 @@
       referrer_policy_no_default);
 }
 
-void SecurityPolicy::AddOriginTrustworthyWhiteList(const String& origin) {
+void SecurityPolicy::AddOriginToTrustworthySafelist(
+    const String& origin_or_pattern) {
 #if DCHECK_IS_ON()
   // Must be called before we start other threads.
   DCHECK(WTF::IsBeforeThreadCreated());
 #endif
-  TrustworthyOriginSet().insert(origin);
+  // Origins and hostname patterns must be canonicalized (including
+  // canonicalization to 8-bit strings) before being inserted into
+  // TrustworthyOriginSafelist().
+  CHECK(origin_or_pattern.Is8Bit());
+  TrustworthyOriginSafelist().insert(origin_or_pattern);
 }
 
-bool SecurityPolicy::IsOriginWhiteListedTrustworthy(
+bool SecurityPolicy::IsOriginTrustworthySafelisted(
     const SecurityOrigin& origin) {
-  // Early return if there are no whitelisted origins to avoid unnecessary
-  // allocations, copies, and frees.
-  if (origin.IsOpaque() || TrustworthyOriginSet().IsEmpty())
+  // Early return if |origin| cannot possibly be matched.
+  if (origin.IsOpaque() || TrustworthyOriginSafelist().IsEmpty())
     return false;
-  if (TrustworthyOriginSet().Contains(origin.ToRawString()))
+
+  if (TrustworthyOriginSafelist().Contains(origin.ToRawString()))
     return true;
 
   // KURL and SecurityOrigin hosts should be canonicalized to 8-bit strings.
   CHECK(origin.Host().Is8Bit());
   StringUTF8Adaptor host_adaptor(origin.Host());
-  for (const auto& origin_or_pattern : TrustworthyOriginSet()) {
-    // Origins and hostname patterns are expected to be canonicalized (including
-    // canonicalization to 8-bit strings) before being inserted into the
-    // TrustworthyOriginSet().
-    CHECK(origin_or_pattern.Is8Bit());
+  for (const auto& origin_or_pattern : TrustworthyOriginSafelist()) {
     StringUTF8Adaptor origin_or_pattern_adaptor(origin_or_pattern);
     if (base::MatchPattern(host_adaptor.AsStringPiece(),
                            origin_or_pattern_adaptor.AsStringPiece())) {
@@ -216,11 +217,11 @@
   return false;
 }
 
-bool SecurityPolicy::IsUrlWhiteListedTrustworthy(const KURL& url) {
+bool SecurityPolicy::IsUrlTrustworthySafelisted(const KURL& url) {
   // Early return to avoid initializing the SecurityOrigin.
-  if (TrustworthyOriginSet().IsEmpty())
+  if (TrustworthyOriginSafelist().IsEmpty())
     return false;
-  return IsOriginWhiteListedTrustworthy(*SecurityOrigin::Create(url).get());
+  return IsOriginTrustworthySafelisted(*SecurityOrigin::Create(url).get());
 }
 
 bool SecurityPolicy::IsOriginAccessAllowed(
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.h b/third_party/blink/renderer/platform/weborigin/security_policy.h
index a3ebc1e8..4790fef 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.h
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.h
@@ -87,9 +87,9 @@
   static bool IsOriginAccessToURLAllowed(const SecurityOrigin* active_origin,
                                          const KURL&);
 
-  static void AddOriginTrustworthyWhiteList(const String&);
-  static bool IsOriginWhiteListedTrustworthy(const SecurityOrigin&);
-  static bool IsUrlWhiteListedTrustworthy(const KURL&);
+  static void AddOriginToTrustworthySafelist(const String&);
+  static bool IsOriginTrustworthySafelisted(const SecurityOrigin&);
+  static bool IsUrlTrustworthySafelisted(const KURL&);
 
   static bool ReferrerPolicyFromString(const String& policy,
                                        ReferrerPolicyLegacyKeywordsSupport,
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
index ec1e5212..931db62 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
@@ -291,7 +291,7 @@
   }
 }
 
-TEST(SecurityPolicyTest, TrustworthyWhiteList) {
+TEST(SecurityPolicyTest, TrustworthySafelist) {
   const char* insecure_urls[] = {
       "http://a.test/path/to/file.html", "http://b.test/path/to/file.html",
       "blob:http://c.test/b3aae9c8-7f90-440d-8d7c-43aa20d72fde",
@@ -302,11 +302,11 @@
     scoped_refptr<const SecurityOrigin> origin =
         SecurityOrigin::CreateFromString(url);
     EXPECT_FALSE(origin->IsPotentiallyTrustworthy());
-    SecurityPolicy::AddOriginTrustworthyWhiteList(origin->ToString());
+    SecurityPolicy::AddOriginToTrustworthySafelist(origin->ToString());
     EXPECT_TRUE(origin->IsPotentiallyTrustworthy());
   }
 
-  // Tests that adding URLs that have inner-urls to the whitelist
+  // Tests that adding URLs that have inner-urls to the safelist
   // takes effect on the origins of the inner-urls (and vice versa).
   struct TestCase {
     const char* url;
@@ -329,7 +329,7 @@
 
     EXPECT_FALSE(origin1->IsPotentiallyTrustworthy());
     EXPECT_FALSE(origin2->IsPotentiallyTrustworthy());
-    SecurityPolicy::AddOriginTrustworthyWhiteList(origin1->ToString());
+    SecurityPolicy::AddOriginToTrustworthySafelist(origin1->ToString());
     EXPECT_TRUE(origin1->IsPotentiallyTrustworthy());
     EXPECT_TRUE(origin2->IsPotentiallyTrustworthy());
   }
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 4a9876e7..e93f44b 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -393,6 +393,12 @@
         ],
     },
     {
+        'paths': ['third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.cc'],
+        'allowed': [
+            'base::MemoryPressureListener',
+        ],
+    },
+    {
         'paths': ['third_party/blink/renderer/core/animation'],
         'allowed': [
             '[a-z_]+_functions::.+',
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
index 85db0d7..3e57f714 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
@@ -260,7 +260,7 @@
 crbug.com/591099 external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Pass ]
 crbug.com/591099 external/wpt/fetch/api/request/request-keepalive-quota.html?include=slow-2 [ Pass ]
 crbug.com/591099 external/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html [ Pass ]
-crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure Pass ]
+crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/html/user-activation/activation-transfer-without-click.tentative.html [ Pass ]
 crbug.com/845902 external/wpt/quirks/line-height-trailing-collapsable-whitespace.html [ Pass ]
@@ -274,10 +274,9 @@
 crbug.com/591099 fast/backgrounds/quirks-mode-line-box-backgrounds.html [ Failure ]
 crbug.com/591099 fast/block/float/4145535Crash.html [ Pass ]
 crbug.com/591099 fast/borders/inline-mask-overlay-image-outset-vertical-rl.html [ Failure ]
-crbug.com/591099 fast/canvas/OffscreenCanvas-copyImage.html [ Failure Pass ]
-crbug.com/591099 fast/css-intrinsic-dimensions/height-css-tables-collapsed.html [ Failure Pass ]
+crbug.com/591099 fast/canvas/OffscreenCanvas-copyImage.html [ Pass ]
 crbug.com/591099 fast/css-intrinsic-dimensions/height-positioned.html [ Pass ]
-crbug.com/591099 fast/css-intrinsic-dimensions/height-tables.html [ Pass ]
+crbug.com/591099 fast/css-intrinsic-dimensions/height-tables.html [ Failure Pass ]
 crbug.com/591099 fast/css/absolute-inline-alignment-2.html [ Pass ]
 crbug.com/835484 fast/css/outline-narrowLine.html [ Failure ]
 crbug.com/591099 fast/css/outline-offset-large.html [ Failure ]
@@ -287,7 +286,6 @@
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects-continuation.html [ Failure ]
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects-list-translate.html [ Failure ]
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
-crbug.com/591099 fast/filesystem/file-writer-abort-depth.html [ Crash Pass ]
 crbug.com/889721 fast/inline/outline-continuations.html [ Failure ]
 crbug.com/591099 fast/multicol/border-radius-clipped-layer.html [ Pass ]
 crbug.com/591099 fast/peerconnection/RTCPeerConnection-many.html [ Pass ]
@@ -321,7 +319,6 @@
 crbug.com/591099 paint/invalidation/svg/svg-background-partial-redraw.html [ Failure ]
 crbug.com/591099 paint/invalidation/svg/transform-focus-ring-repaint.html [ Failure ]
 crbug.com/591099 printing/iframe-svg-in-object-print.html [ Failure ]
-crbug.com/591099 scrollbars/auto-scrollbar-fit-content.html [ Failure ]
 crbug.com/591099 storage/indexeddb/objectstore-cursor.html [ Pass ]
 crbug.com/591099 svg/zoom/page/zoom-svg-float-border-padding.xml [ Pass ]
 crbug.com/591099 tables/mozilla/bugs/bug14159-1.html [ Pass ]
@@ -341,7 +338,7 @@
 crbug.com/591099 virtual/display-lock/display-lock/lock-before-append/measure-updated-layout.html [ Failure ]
 crbug.com/591099 virtual/exotic-color-space/ [ Skip ]
 crbug.com/591099 virtual/gpu-rasterization/images/color-profile-image-filter-all.html [ Pass ]
-crbug.com/591099 virtual/gpu/fast/canvas/OffscreenCanvas-copyImage.html [ Pass ]
+crbug.com/591099 virtual/gpu/fast/canvas/OffscreenCanvas-copyImage.html [ Failure Pass ]
 crbug.com/591099 virtual/gpu/fast/canvas/OffscreenCanvas-filter.html [ Pass ]
 crbug.com/591099 virtual/gpu/fast/canvas/canvas-blend-image.html [ Pass ]
 crbug.com/591099 virtual/gpu/fast/canvas/canvas-blending-color-over-pattern.html [ Pass ]
@@ -374,4 +371,4 @@
 crbug.com/591099 virtual/video-surface-layer/media/color-profile-video-seek-filter.html [ Pass ]
 crbug.com/591099 virtual/video-surface-layer/media/stable/video-object-fit-stable.html [ Failure Pass ]
 crbug.com/591099 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCIceTransport-extension.https.html [ Failure Pass ]
-crbug.com/591099 vr/getFrameData_oneframeupdate.html [ Failure Pass ]
+crbug.com/591099 vr/getFrameData_oneframeupdate.html [ Pass ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 888a01d..5268d2ac 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6306,3 +6306,7 @@
 crbug.com/951774 [ Linux ] virtual/video-surface-layer/media/controls/modern/overlay-play-button-tap-to-hide.html [ Pass Timeout ]
 crbug.com/951811 [ Mac10.13 ] external/wpt/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html [ Pass Timeout ]
 crbug.com/802029 [ Mac10.13 Debug ] fast/dom/shadow/focus-controller-recursion-crash.html [ Pass Timeout ]
+
+# Sheriff 2019-04-15
+crbug.com/952708 external/wpt/css/css-paint-api/parse-input-arguments-018.https.html [ Pass Failure ]
+crbug.com/952717 [ Mac10.13 ] virtual/outofblink-cors/http/tests/xmlhttprequest/redirect-cross-origin-post.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/accessibility/add-to-menu-list-crashes.html b/third_party/blink/web_tests/accessibility/add-to-menu-list-crashes.html
index 44e23be..86eab4c 100644
--- a/third_party/blink/web_tests/accessibility/add-to-menu-list-crashes.html
+++ b/third_party/blink/web_tests/accessibility/add-to-menu-list-crashes.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -13,7 +12,7 @@
 </select>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var menulist = document.getElementById("menulist");
     menulist.focus();
     window.accessibleMenulist = accessibilityController.focusedElement;
diff --git a/third_party/blink/web_tests/accessibility/animation-policy.html b/third_party/blink/web_tests/accessibility/animation-policy.html
index f4bbb08..f53e84e 100644
--- a/third_party/blink/web_tests/accessibility/animation-policy.html
+++ b/third_party/blink/web_tests/accessibility/animation-policy.html
@@ -1,4 +1,3 @@
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script>
 var updated = false;
 var prevTime;
diff --git a/third_party/blink/web_tests/accessibility/aom-boolean-properties.html b/third_party/blink/web_tests/accessibility/aom-boolean-properties.html
index 032da15..4abc1e19 100644
--- a/third_party/blink/web_tests/accessibility/aom-boolean-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-boolean-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 <div role="region" id="atomic" aria-atomic="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("atomic");
     var axNode = accessibilityController.accessibleElementById("atomic");
     assert_equals(axNode.isAtomic, true);
@@ -36,7 +35,7 @@
 <div role="region" id="atomic2"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("atomic2");
     var axNode = accessibilityController.accessibleElementById("atomic2");
     assert_equals(axNode.isAtomic, false);
@@ -48,7 +47,7 @@
 <div role="status" id="busy"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("busy");
     var axNode = accessibilityController.accessibleElementById("busy");
     assert_equals(axNode.isBusy, false);
@@ -60,7 +59,7 @@
 <div role="checkbox" id="disabled"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("disabled");
     var axNode = accessibilityController.accessibleElementById("disabled");
     assert_equals(axNode.restriction, "none");
@@ -72,7 +71,7 @@
 <div role="button" id="expanded"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("expanded");
     var axNode = accessibilityController.accessibleElementById("expanded");
     assert_equals(axNode.isExpanded, false);
@@ -84,7 +83,7 @@
 <div role="region" id="will-be-hidden"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("will-be-hidden");
     var axNode = accessibilityController.accessibleElementById("will-be-hidden");
     assert_true(axNode != null);
@@ -99,7 +98,7 @@
 <div role="dialog" id="modal"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("modal");
     var axNode = accessibilityController.accessibleElementById("modal");
     assert_equals(axNode.isModal, false);
@@ -111,7 +110,7 @@
 <div role="textbox" id="multiline"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("multiline");
     var axNode = accessibilityController.accessibleElementById("multiline");
     assert_equals(axNode.isMultiLine, false);
@@ -123,7 +122,7 @@
 <div role="listbox" id="multiselectable"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("multiselectable");
     var axNode = accessibilityController.accessibleElementById("multiselectable");
     assert_equals(axNode.isMultiSelectable, false);
@@ -135,7 +134,7 @@
 <div role="textbox" id="readOnly" tabindex="0"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("readOnly");
     var axNode = accessibilityController.accessibleElementById("readOnly");
     assert_equals(axNode.isAttributeSettable("AXValue"), true);
@@ -147,7 +146,7 @@
 <div role="textbox" id="required"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("required");
     var axNode = accessibilityController.accessibleElementById("required");
     assert_equals(axNode.isRequired, false);
@@ -159,7 +158,7 @@
 <div role="option" id="selected" tabindex="-1"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("selected");
     var axNode = accessibilityController.accessibleElementById("selected");
     assert_equals(axNode.isSelected, false);
diff --git a/third_party/blink/web_tests/accessibility/aom-click-action.html b/third_party/blink/web_tests/accessibility/aom-click-action.html
index cb7f07f..a1dc7fc 100644
--- a/third_party/blink/web_tests/accessibility/aom-click-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-click-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <button id="target1">Target</button>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target1 = document.getElementById("target1");
     var axTarget1 = accessibilityController.accessibleElementById("target1");
@@ -42,7 +41,7 @@
 <button id="target2">Target</button>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target2 = document.getElementById("target2");
     var axTarget2 = accessibilityController.accessibleElementById("target2");
@@ -63,7 +62,7 @@
 <button id="target3b">Target with preventDefault</button>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target3a = document.getElementById("target3a");
     var target3b = document.getElementById("target3b");
@@ -109,7 +108,7 @@
 </p>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var p4 = document.getElementById("p4");
     var axTarget4 = accessibilityController.accessibleElementById("target4");
@@ -135,7 +134,7 @@
 </section>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var section5 = document.getElementById("section5");
     var ul5 = document.getElementById("ul5");
@@ -204,7 +203,7 @@
 </section>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var ul6a = document.getElementById("ul6a");
     var ul6b = document.getElementById("ul6b");
@@ -233,7 +232,7 @@
 <p id="container7" aria-label="Container7"></p>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var container7 = document.getElementById("container7");
     var axContainer7 = accessibilityController.accessibleElementById(
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
index b16664f5..5213ea57 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -16,7 +15,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-boolean-properties.html b/third_party/blink/web_tests/accessibility/aom-computed-boolean-properties.html
index fe4597ff..098d9ed 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-boolean-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-boolean-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -17,7 +16,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-float-properties.html b/third_party/blink/web_tests/accessibility/aom-computed-float-properties.html
index 1f36982..e393be32 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-float-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-float-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -17,7 +16,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-int-properties.html b/third_party/blink/web_tests/accessibility/aom-computed-int-properties.html
index 63a49b25..19a2887 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-int-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-int-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-relation-accessors.html b/third_party/blink/web_tests/accessibility/aom-computed-relation-accessors.html
index 636ca5fa..7e94c286 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-relation-accessors.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-relation-accessors.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-string-properties.html b/third_party/blink/web_tests/accessibility/aom-computed-string-properties.html
index dceb61a..2393dfc7 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-string-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-string-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -16,7 +15,7 @@
 
 <script>
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aom-contextmenu-action.html b/third_party/blink/web_tests/accessibility/aom-contextmenu-action.html
index 6f67436..9c15baaf 100644
--- a/third_party/blink/web_tests/accessibility/aom-contextmenu-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-contextmenu-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <button id="context_menu_target">Context Menu Target</button>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target = document.getElementById("context_menu_target");
     var axTarget = accessibilityController.accessibleElementById("context_menu_target");
diff --git a/third_party/blink/web_tests/accessibility/aom-decrement-action.html b/third_party/blink/web_tests/accessibility/aom-decrement-action.html
index 505c272..ceefabd 100644
--- a/third_party/blink/web_tests/accessibility/aom-decrement-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-decrement-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <input type=range min=1 max=5 value=3 id="decrement_target">
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target = document.getElementById("decrement_target");
     var axTarget = accessibilityController.accessibleElementById("decrement_target");
diff --git a/third_party/blink/web_tests/accessibility/aom-float-properties.html b/third_party/blink/web_tests/accessibility/aom-float-properties.html
index 05b2cb5..7b832c1 100644
--- a/third_party/blink/web_tests/accessibility/aom-float-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-float-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 <div role="slider" id="slider" aria-valuenow="5"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("slider");
     var axNode = accessibilityController.accessibleElementById("slider");
     assert_equals(axNode.intValue, 5);
@@ -36,7 +35,7 @@
 <div role="slider" id="slider2"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("slider2");
     var axNode = accessibilityController.accessibleElementById("slider2");
     assert_equals(axNode.intValue, 50);
@@ -54,7 +53,7 @@
 <div role="spinbutton" id="spinbutton"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("spinbutton");
     var axNode = accessibilityController.accessibleElementById("spinbutton");
     assert_equals(axNode.intValue, 0);
diff --git a/third_party/blink/web_tests/accessibility/aom-focus-action.html b/third_party/blink/web_tests/accessibility/aom-focus-action.html
index 0eac280..a847b36 100644
--- a/third_party/blink/web_tests/accessibility/aom-focus-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-focus-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <input id="focus_target">
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target = document.getElementById("focus_target");
     var axTarget = accessibilityController.accessibleElementById("focus_target");
diff --git a/third_party/blink/web_tests/accessibility/aom-increment-action.html b/third_party/blink/web_tests/accessibility/aom-increment-action.html
index b423869..2f15097 100644
--- a/third_party/blink/web_tests/accessibility/aom-increment-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-increment-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <input type=range min=1 max=5 value=3 id="increment_target">
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target = document.getElementById("increment_target");
     var axTarget = accessibilityController.accessibleElementById("increment_target");
diff --git a/third_party/blink/web_tests/accessibility/aom-int-properties.html b/third_party/blink/web_tests/accessibility/aom-int-properties.html
index 27f43b0..4d8d5b0 100644
--- a/third_party/blink/web_tests/accessibility/aom-int-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-int-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 <div role=heading id=heading>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("heading");
     var axNode = accessibilityController.accessibleElementById("heading");
     node.accessibleNode.level = 3;
@@ -35,7 +34,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("listitem");
     var axNode = accessibilityController.accessibleElementById("listitem");
     node.accessibleNode.posInSet = 9;
diff --git a/third_party/blink/web_tests/accessibility/aom-relation-list-properties.html b/third_party/blink/web_tests/accessibility/aom-relation-list-properties.html
index 524cef3..ab0259a 100644
--- a/third_party/blink/web_tests/accessibility/aom-relation-list-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-relation-list-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -13,7 +12,7 @@
 -->
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -26,7 +25,7 @@
 <label id="l4">L4</label>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var input = document.getElementById("input");
     var axInput = accessibilityController.accessibleElementById("input");
     assert_equals(axInput.name, "L1 L2");
@@ -69,7 +68,7 @@
 <input id="input3" aria-describedby="l1 l2">
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var input = document.getElementById("input3");
     var axInput = accessibilityController.accessibleElementById("input3");
     assert_equals(axInput.description, "L1 L2");
@@ -117,7 +116,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTab1 = accessibilityController.accessibleElementById("tablist_1").childAtIndex(0);
     var axPanel1 = accessibilityController.accessibleElementById("panel_1");
     var axPanel2 = accessibilityController.accessibleElementById("panel_2");
@@ -158,7 +157,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axItem1 = accessibilityController.accessibleElementById("item1");
     var axItem2 = accessibilityController.accessibleElementById("item2");
     var axItem3 = accessibilityController.accessibleElementById("item3");
@@ -201,7 +200,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axList1 = accessibilityController.accessibleElementById("list1");
     assert_equals(axList1.role, "AXRole: AXListBox");
diff --git a/third_party/blink/web_tests/accessibility/aom-relation-properties.html b/third_party/blink/web_tests/accessibility/aom-relation-properties.html
index b0c4872..8278a06 100644
--- a/third_party/blink/web_tests/accessibility/aom-relation-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-relation-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -13,7 +12,7 @@
 -->
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -25,7 +24,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var listbox = document.getElementById("listbox");
     var option1 = document.getElementById("option1");
     listbox.focus();
@@ -46,7 +45,7 @@
 <div id="details">Details</div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var input1 = document.getElementById("input1");
     var details = document.getElementById("details");
     var axInput1 = accessibilityController.accessibleElementById("input1");
@@ -63,7 +62,7 @@
 <div id="errorMessage">ErrorMessage</div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var input2 = document.getElementById("input2");
     var errorMessage = document.getElementById("errorMessage");
     var axInput2 = accessibilityController.accessibleElementById("input2");
diff --git a/third_party/blink/web_tests/accessibility/aom-scroll-action.html b/third_party/blink/web_tests/accessibility/aom-scroll-action.html
index 9ba4640..15864642 100644
--- a/third_party/blink/web_tests/accessibility/aom-scroll-action.html
+++ b/third_party/blink/web_tests/accessibility/aom-scroll-action.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../http/tests/resources/permissions-helper.js"></script>
 <script src="resources/aom-helper.js"></script>
 
@@ -14,7 +13,7 @@
 
 -->
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -22,7 +21,7 @@
 <input id="scroll_target" style="margin-top: 1000px;">
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   enableAccessibilityEventsPermission().then(function() {
     var target = document.getElementById("scroll_target");
     var axTarget = accessibilityController.accessibleElementById("scroll_target");
diff --git a/third_party/blink/web_tests/accessibility/aom-string-properties.html b/third_party/blink/web_tests/accessibility/aom-string-properties.html
index e1f56d6..5dc7348 100644
--- a/third_party/blink/web_tests/accessibility/aom-string-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-string-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -13,7 +12,7 @@
 -->
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -21,7 +20,7 @@
 <input role="combobox" id="autocomplete">
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("autocomplete");
     var axNode = accessibilityController.accessibleElementById("autocomplete");
     assert_equals(axNode.autocomplete, "");
@@ -33,7 +32,7 @@
 <div role="checkbox" id="checked"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("checked");
     var axNode = accessibilityController.accessibleElementById("checked");
     assert_equals(axNode.checked, "false");
@@ -53,7 +52,7 @@
 <div role="tab" id="current"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("current");
     var axNode = accessibilityController.accessibleElementById("current");
     assert_equals(axNode.current, "");
@@ -65,7 +64,7 @@
 <div role="textbox" id="hasPopUp" aria-haspopup="false"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("hasPopUp");
     var axNode = accessibilityController.accessibleElementById("hasPopUp");
     assert_equals(axNode.hasPopup, "");
@@ -80,7 +79,7 @@
 <div role="textbox" id="invalid"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("invalid");
     var axNode = accessibilityController.accessibleElementById("invalid");
     assert_equals(axNode.invalid, "");
@@ -92,7 +91,7 @@
 <div role="button" id="keyShortcuts"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("keyShortcuts");
     var axNode = accessibilityController.accessibleElementById("keyShortcuts");
     assert_equals(axNode.keyShortcuts, "");
@@ -104,7 +103,7 @@
 <div role="heading" id="label">Inner text</div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("label");
     var axNode = accessibilityController.accessibleElementById("label");
     assert_equals(axNode.name, "Inner text");
@@ -116,7 +115,7 @@
 <div role="banner" id="live"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("live");
     var axNode = accessibilityController.accessibleElementById("live");
     assert_equals(axNode.live, "");
@@ -128,7 +127,7 @@
 <div role="slider" id="orientation"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("orientation");
     var axNode = accessibilityController.accessibleElementById("orientation");
     assert_equals(axNode.orientation, "AXOrientation: AXHorizontalOrientation");
@@ -140,7 +139,7 @@
 <input id="placeholder">
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("placeholder");
     var axNode = accessibilityController.accessibleElementById("placeholder");
     assert_equals(axNode.name, "");
@@ -153,7 +152,7 @@
 <div role="banner" id="relevant"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("relevant");
     var axNode = accessibilityController.accessibleElementById("relevant");
     assert_equals(axNode.relevant, "additions text");
@@ -165,7 +164,7 @@
 <div role="button" id="role"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("role");
     var axNode = accessibilityController.accessibleElementById("role");
     assert_equals(axNode.role, "AXRole: AXButton");
@@ -183,7 +182,7 @@
 <div role="button" id="roleDescription"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("roleDescription");
     var axNode = accessibilityController.accessibleElementById("roleDescription");
     assert_equals(axNode.roleDescription, "");
@@ -195,7 +194,7 @@
 <div role="columnheader" id="sort"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("sort");
     var axNode = accessibilityController.accessibleElementById("sort");
     assert_equals(axNode.sort, "");
@@ -207,7 +206,7 @@
 <div role="slider" id="valueText" aria-valuetext="5"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var node = document.getElementById("valueText");
     var axNode = accessibilityController.accessibleElementById("valueText");
     assert_equals(axNode.valueDescription, "AXValueDescription: 5");
diff --git a/third_party/blink/web_tests/accessibility/aom-virtual-bool-properties.html b/third_party/blink/web_tests/accessibility/aom-virtual-bool-properties.html
index 494c49e..a0ccf6e 100644
--- a/third_party/blink/web_tests/accessibility/aom-virtual-bool-properties.html
+++ b/third_party/blink/web_tests/accessibility/aom-virtual-bool-properties.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -20,7 +19,7 @@
 <div id="atomic"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var parent = document.getElementById("atomic");
     var accessibleNode = new AccessibleNode();
     accessibleNode.role = "region";
@@ -38,7 +37,7 @@
 <div id="busy"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var parent = document.getElementById("busy");
     var accessibleNode = new AccessibleNode();
     accessibleNode.role = "status";
@@ -56,7 +55,7 @@
 <div id="disabled"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var parent = document.getElementById("disabled");
     var node = new AccessibleNode();
     node.role = "checkbox";
diff --git a/third_party/blink/web_tests/accessibility/aom-virtual.html b/third_party/blink/web_tests/accessibility/aom-virtual.html
index 82fe915..d32eab8 100644
--- a/third_party/blink/web_tests/accessibility/aom-virtual.html
+++ b/third_party/blink/web_tests/accessibility/aom-virtual.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -13,7 +12,7 @@
 -->
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -21,7 +20,7 @@
 <div id="container"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_throws('InvalidAccessError', function() {
         var container = document.getElementById("container");
         container.accessibleNode.appendChild(document.body.accessibleNode);
@@ -30,7 +29,7 @@
 </script>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = document.getElementById("container");
     var child = new AccessibleNode();
     child.role = "button";
@@ -48,7 +47,7 @@
 <div id="container2"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = document.getElementById("container2");
 
     var list = new AccessibleNode();
@@ -77,7 +76,7 @@
 </script>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = document.getElementById("container2");
 
     var list = new AccessibleNode();
@@ -111,7 +110,7 @@
 </script>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = document.getElementById("container2");
 
     var list1 = new AccessibleNode();
diff --git a/third_party/blink/web_tests/accessibility/aom.html b/third_party/blink/web_tests/accessibility/aom.html
index 8e6875be..abba524 100644
--- a/third_party/blink/web_tests/accessibility/aom.html
+++ b/third_party/blink/web_tests/accessibility/aom.html
@@ -2,7 +2,6 @@
 <script src="../resources/gc.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!--
 
@@ -13,7 +12,7 @@
 -->
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
 }, "Make sure that Accessibility Object Model is enabled");
 </script>
@@ -21,7 +20,7 @@
 <button id="button1">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button1");
     assert_equals(button.nodeType, Node.ELEMENT_NODE);
     assert_true(Boolean(button.accessibleNode));
@@ -31,7 +30,7 @@
 <button id="button2">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button2");
     var staticText = button.firstChild;
     assert_equals(staticText.nodeType, Node.TEXT_NODE);
@@ -42,7 +41,7 @@
 <button id="button3">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button2");
     var aomButton = button.accessibleNode;
 
@@ -56,7 +55,7 @@
 <button id="button4">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button4");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("button4");
@@ -76,7 +75,7 @@
 <button id="button5">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button5");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("buttont");
@@ -92,7 +91,7 @@
 <button id="button6">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button6");
     var aomButton = button.accessibleNode;
     console.log('aomButton: ' + aomButton);
@@ -116,7 +115,7 @@
 <button id="button7">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button7");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("button7");
@@ -139,7 +138,7 @@
 <button id="button8">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button8");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("button8");
@@ -156,7 +155,7 @@
 <button id="button9">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button9");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("button9");
@@ -180,7 +179,7 @@
 <button id="button10">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var button = document.getElementById("button10");
     var aomButton = button.accessibleNode;
     var axButton = accessibilityController.accessibleElementById("button10");
@@ -213,7 +212,7 @@
 <button id="button11">Click Me</button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var aomButton;
     (function() {
         var button = document.getElementById("button11");
diff --git a/third_party/blink/web_tests/accessibility/appearance-affects-role.html b/third_party/blink/web_tests/accessibility/appearance-affects-role.html
index 9679569..959c991 100644
--- a/third_party/blink/web_tests/accessibility/appearance-affects-role.html
+++ b/third_party/blink/web_tests/accessibility/appearance-affects-role.html
@@ -2,7 +2,6 @@
 <title>Accessibility: elements with -webkit-appearance: none retain the correct role.</title>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <!--
 This test checks that elements with -webkit-appearance: none, do not lose their
 AXRole.
@@ -58,95 +57,95 @@
         assert_equals(axObject.role, expectedRole);
     }
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("button", "AXRole: AXButton");
     }, "Test computed AX role for <button>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("ibutton", "AXRole: AXButton");
     }, "Test computed AX role for <input type=button>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("icolor", "AXRole: AXColorWell");
     }, "Test computed AX role for <input type=color>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("idate", "AXRole: AXDateField");
     }, "Test computed AX role for <input type=date>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("idatetimelocal", "AXRole: AXDateTimeField");
     }, "Test computed AX role for <input type=datetime-local>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("ifile", "AXRole: AXButton");
     }, "Test computed AX role for <input type=file>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("imonth", "AXRole: AXDateTimeField");
     }, "Test computed AX role for <input type=month>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("inumber", "AXRole: AXSpinButton");
     }, "Test computed AX role for <input type=number>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("ipassword", "AXRole: AXTextField");
     }, "Test computed AX role for <input type=password>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("iradio", "AXRole: AXRadioButton");
     }, "Test computed AX role for <input type=radio>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("irange", "AXRole: AXSlider");
     }, "Test computed AX role for <input type=range>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("ireset", "AXRole: AXButton");
     }, "Test computed AX role for <input type=reset>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("isearch", "AXRole: AXTextField");
     }, "Test computed AX role for <input type=search>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("isubmit", "AXRole: AXButton");
     }, "Test computed AX role for <input type=submit>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("itext", "AXRole: AXTextField");
     }, "Test computed AX role for <input type=text>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("itime", "AXRole: AXInputTime");
     }, "Test computed AX role for <input type=time>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("iweek", "AXRole: AXDateTimeField");
     }, "Test computed AX role for <input type=week>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("idatalist", "AXRole: AXTextFieldWithComboBox");
     }, "Test computed AX role for <input list=datalist>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("meter", "AXRole: AXMeter");
     }, "Test computed AX role for <meter>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("progress", "AXRole: AXProgressIndicator");
     }, "Test computed AX role for <progress>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("textarea", "AXRole: AXTextField");
     }, "Test computed AX role for <textarea>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("select", "AXRole: AXPopUpButton");
     }, "Test computed AX role for <select>");
 
-    test_after_layout_and_paint(function () {
+    test(function () {
         check("multiselect", "AXRole: AXListBox");
     }, "Test computed AX role for <select multiple>");
 </script>
diff --git a/third_party/blink/web_tests/accessibility/aria-activedescendant-events.html b/third_party/blink/web_tests/accessibility/aria-activedescendant-events.html
index db8c62c..02c3af4 100644
--- a/third_party/blink/web_tests/accessibility/aria-activedescendant-events.html
+++ b/third_party/blink/web_tests/accessibility/aria-activedescendant-events.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="contenteditable-textbox" role="textbox" contenteditable="true"
     aria-expanded="true" aria-haspopup="true" aria-autocomplete="list"
@@ -101,7 +100,7 @@
     t.step(() => { testEventExpectations(t, nextIds[0], nextIds[1]) });
 }
 
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     testNext(t);
 }, "Changing active descendant triggers MarkDirty");
 </script>
diff --git a/third_party/blink/web_tests/accessibility/aria-activedescendant.html b/third_party/blink/web_tests/accessibility/aria-activedescendant.html
index b29c4526..6b55949 100644
--- a/third_party/blink/web_tests/accessibility/aria-activedescendant.html
+++ b/third_party/blink/web_tests/accessibility/aria-activedescendant.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="contenteditable-combobox" role="combobox" contenteditable="true"
     aria-expanded="true" aria-haspopup="menu" aria-autocomplete="list"
@@ -121,7 +120,7 @@
     t.step(() => { testExpectations(t, nextIds[0], nextIds[1], nextIds[2]) });
 }
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     testNext(t);
 }, 'Changing active descendant correctly sets focused element');
 </script>
diff --git a/third_party/blink/web_tests/accessibility/aria-checkbox-checked-mixed.html b/third_party/blink/web_tests/accessibility/aria-checkbox-checked-mixed.html
index 3dee4ff6..6951b76 100644
--- a/third_party/blink/web_tests/accessibility/aria-checkbox-checked-mixed.html
+++ b/third_party/blink/web_tests/accessibility/aria-checkbox-checked-mixed.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -25,7 +24,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     let roles = ["AXRole: AXCheckBox",
                  "AXRole: AXMenuItemCheckBox",
                  "AXRole: AXRadioButton",
diff --git a/third_party/blink/web_tests/accessibility/aria-checkbox-sends-notification.html b/third_party/blink/web_tests/accessibility/aria-checkbox-sends-notification.html
index 6ae096bf..9ed2976 100644
--- a/third_party/blink/web_tests/accessibility/aria-checkbox-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/aria-checkbox-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -12,7 +11,7 @@
 <p id="description"></p>
 <div id="console"></div>
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     console.log('t ' + t);
     var accessibleCheckbox = accessibilityController.accessibleElementById("checkbox1");
     var notificationCount = 0;
diff --git a/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay-add.html b/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay-add.html
index b7509847..7229893 100644
--- a/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay-add.html
+++ b/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay-add.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <!-- Start with -->
 <div role="main">
   <input id="comboBox"
@@ -17,7 +16,7 @@
 </div>
 
 <script>
-  async_test_after_layout_and_paint((t) => {
+  async_test((t) => {
     let comboBox = accessibilityController.accessibleElementById('comboBox');
     document.getElementById('container').innerHTML =
           '<ul id="stateList" role="listbox"><li id="state1" role="option">Alabama</li><li id="state2" role="option">Alaska</li></ul>';
diff --git a/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay.html b/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay.html
index f9fb196..f3c86931 100644
--- a/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay.html
+++ b/third_party/blink/web_tests/accessibility/aria-combo-box-with-delay.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="main">
   <input id="comboBox"
@@ -29,7 +28,7 @@
     document.querySelector('ul').style.display = 'block';
   });
 
-  test_after_layout_and_paint((t) => {
+  test((t) => {
       assert_true(window.comboBox.isFocused, 'Combo box should be focused.');
       let state1 = accessibilityController.accessibleElementById('state1');
       assert_false(state1.isSelected, 'State1 should not be selected.');
@@ -43,7 +42,7 @@
       assert_false(state2.isFocused, 'State2 should not be focused.');
   }, 'An option with an activedescendant pointing to it is selected.');
 
-  test_after_layout_and_paint((t) => {
+  test((t) => {
       document.querySelector('input').setAttribute('aria-activedescendant', 'state1');
 
       assert_true(window.comboBox.isFocused, 'Combo box should be focused.');
diff --git a/third_party/blink/web_tests/accessibility/aria-combo-box.html b/third_party/blink/web_tests/accessibility/aria-combo-box.html
index 202d65f..db2175b 100644
--- a/third_party/blink/web_tests/accessibility/aria-combo-box.html
+++ b/third_party/blink/web_tests/accessibility/aria-combo-box.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <input role="combobox"
     type="search"
@@ -47,7 +46,7 @@
 </ul>
 
 <script>
-  test_after_layout_and_paint(() => {
+  test(() => {
     for (let i = 0; i < 3; ++i) {
       document.querySelectorAll('input')[i].focus();
 
@@ -64,7 +63,7 @@
     }
   }, 'An option with an activedescendant pointing to it is selected.');
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     for (let i = 0; i < 3; ++i) {
       document.querySelectorAll('input')[i].focus();
       document.querySelectorAll('input')[i].setAttribute('aria-activedescendant', 'state1' + i);
diff --git a/third_party/blink/web_tests/accessibility/aria-controls-with-tabs.html b/third_party/blink/web_tests/accessibility/aria-controls-with-tabs.html
index c78d6c8e..2ae5e97c 100644
--- a/third_party/blink/web_tests/accessibility/aria-controls-with-tabs.html
+++ b/third_party/blink/web_tests/accessibility/aria-controls-with-tabs.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -24,7 +23,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var tabList = accessibilityController.accessibleElementById('tablist_1');
     var tab1 = tabList.childAtIndex(0);
     var tab2 = tabList.childAtIndex(1);
diff --git a/third_party/blink/web_tests/accessibility/aria-controls.html b/third_party/blink/web_tests/accessibility/aria-controls.html
index e352de1f..cdddcc0 100644
--- a/third_party/blink/web_tests/accessibility/aria-controls.html
+++ b/third_party/blink/web_tests/accessibility/aria-controls.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -19,7 +18,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var tab_1 = accessibilityController.accessibleElementById("tablist_1").childAtIndex(0);
     var panel_1 = accessibilityController.accessibleElementById("panel_1");
     var panel_2 = accessibilityController.accessibleElementById("panel_2");
diff --git a/third_party/blink/web_tests/accessibility/aria-disabled.html b/third_party/blink/web_tests/accessibility/aria-disabled.html
index 323e99a..88f5639b 100644
--- a/third_party/blink/web_tests/accessibility/aria-disabled.html
+++ b/third_party/blink/web_tests/accessibility/aria-disabled.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 <input id="text1" type="text" aria-disabled="true" size=20>
@@ -24,7 +23,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var textField = accessibilityController.accessibleElementById("text1");
     assert_equals(textField.restriction, "disabled");
 
diff --git a/third_party/blink/web_tests/accessibility/aria-flowto.html b/third_party/blink/web_tests/accessibility/aria-flowto.html
index 82f17c8..cfe4c4d 100644
--- a/third_party/blink/web_tests/accessibility/aria-flowto.html
+++ b/third_party/blink/web_tests/accessibility/aria-flowto.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -16,7 +15,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var item1 = accessibilityController.accessibleElementById("item1");
     var item2 = accessibilityController.accessibleElementById("item2");
     var item3 = accessibilityController.accessibleElementById("item3");
diff --git a/third_party/blink/web_tests/accessibility/aria-grid-readonly-propagation.html b/third_party/blink/web_tests/accessibility/aria-grid-readonly-propagation.html
index 8677c7e..0f0b1c2 100644
--- a/third_party/blink/web_tests/accessibility/aria-grid-readonly-propagation.html
+++ b/third_party/blink/web_tests/accessibility/aria-grid-readonly-propagation.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="grid" id="grid-readonly-undefined">
   <div role="row">
@@ -36,42 +35,42 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-undef-undef");
     assert_equals(cell.restriction, "none");
 }, "readonly=false on cell where readonly is undefined on grid+cell");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-undef-true");
     assert_equals(cell.restriction, "readOnly");
 }, "readonly=true on a cell");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-undef-false");
     assert_equals(cell.restriction, "none");
 }, "readonly=false on a cell");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-true-undef");
     assert_equals(cell.restriction, "readOnly");
 }, "Propagation of readonly=true from grid to cells");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-true-false");
     assert_equals(cell.restriction, "none");
 }, "Cell readonly=false overrides propagation of grid readonly=true");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-false-undef");
     assert_equals(cell.restriction, "none");
 }, "Propagation of readonly=false from grid to cells");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("cell-false-true");
     assert_equals(cell.restriction, "readOnly");
 }, "Cell readonly=true overrides propagation of grid readonly=false");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var cell = axElementById("treegrid-cell-true-undef");
     assert_equals(cell.restriction, "readOnly");
 }, "Propagation of readonly=true in a readonly treegrid");
diff --git a/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html b/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
index 678dc55..4db72a7 100644
--- a/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
+++ b/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -26,7 +25,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var content = accessibilityController.accessibleElementById("content");
     assert_equals(content.childrenCount, 6);
 
diff --git a/third_party/blink/web_tests/accessibility/aria-hidden-update.html b/third_party/blink/web_tests/accessibility/aria-hidden-update.html
index a27dfa93..7c1a1a0b 100644
--- a/third_party/blink/web_tests/accessibility/aria-hidden-update.html
+++ b/third_party/blink/web_tests/accessibility/aria-hidden-update.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -18,7 +17,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     // Get the parent element.
     var parent = axElementById("parent");
 
diff --git a/third_party/blink/web_tests/accessibility/aria-hidden-updates-alldescendants.html b/third_party/blink/web_tests/accessibility/aria-hidden-updates-alldescendants.html
index 2b95120..50fc3d5cf 100644
--- a/third_party/blink/web_tests/accessibility/aria-hidden-updates-alldescendants.html
+++ b/third_party/blink/web_tests/accessibility/aria-hidden-updates-alldescendants.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -29,7 +28,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var main = axElementById("main");
     assert_equals(main.childrenCount, 1);
 
diff --git a/third_party/blink/web_tests/accessibility/aria-hidden.html b/third_party/blink/web_tests/accessibility/aria-hidden.html
index f49f6d2..157d480 100644
--- a/third_party/blink/web_tests/accessibility/aria-hidden.html
+++ b/third_party/blink/web_tests/accessibility/aria-hidden.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 <h1 aria-hidden="true">h1 <b>test</b></h1>
@@ -12,7 +11,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var body = document.getElementById("body");
     body.focus();
     var h2 = accessibilityController.focusedElement.childAtIndex(0);
diff --git a/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-aria-label.html b/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-aria-label.html
index 495c5c6..9b448a3b 100644
--- a/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-aria-label.html
+++ b/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-aria-label.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -15,7 +14,7 @@
 <span id="theta">Theta</span>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var usingNone = accessibilityController.accessibleElementById("using-none");
     assert_equals(usingNone.name, "Alpha");
 
diff --git a/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-label.html b/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-label.html
index e8921c6..df40ec1a 100644
--- a/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-label.html
+++ b/third_party/blink/web_tests/accessibility/aria-labelledby-overrides-label.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -16,7 +15,7 @@
 <input id="target" aria-labelledby="a b c" value="10">
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var axTarget = accessibilityController.accessibleElementById("target");
     assert_equals(axTarget.name, "A B C");
     assert_equals(axTarget.nameElementCount(), 3);
diff --git a/third_party/blink/web_tests/accessibility/aria-modal.html b/third_party/blink/web_tests/accessibility/aria-modal.html
index 3b2c0e8..7efdbb3 100644
--- a/third_party/blink/web_tests/accessibility/aria-modal.html
+++ b/third_party/blink/web_tests/accessibility/aria-modal.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <dialog id="modal-1">
   <div id="aria-modal-1" role="button" aria-modal="true">
@@ -24,42 +23,42 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axAriaModal1 = axElementById("aria-modal-1");
     assert_equals(axAriaModal1.isModal, false);
 }, "A button can't be modal");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axAriaModal2 = axElementById("aria-modal-2");
     assert_equals(axAriaModal2.isModal, false);
 }, "An ARIA dialog with aria-modal false is not modal");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axAriaModal3 = axElementById("aria-modal-3");
     assert_equals(axAriaModal3.isModal, true);
 }, "An ARIA dialog with aria-modal true is modal");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axAriaModal4 = axElementById("aria-modal-4");
     assert_equals(axAriaModal4.isModal, true);
 }, "An ARIA alertdialog with aria-modal true is modal");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axModal1 = axElementById("modal-1");
     assert_equals(axModal1.isModal, true);
 }, "An HTML dialog is modal after calling showModal() on it");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axModal2 = axElementById("modal-2");
     assert_equals(axModal2, undefined);
 }, "A closed HTML dialog has no accessibility node");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axModal3 = axElementById("modal-3");
     assert_equals(axModal3.isModal, false);
 }, "An open HTML dialog is not modal");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axModal4 = axElementById("modal-4");
     assert_equals(axModal4.isModal, true);
 }, "An open HTML dialog with aria-modal=true is modal");
diff --git a/third_party/blink/web_tests/accessibility/aria-multiselect-state.html b/third_party/blink/web_tests/accessibility/aria-multiselect-state.html
index be3751d..47eebcd 100644
--- a/third_party/blink/web_tests/accessibility/aria-multiselect-state.html
+++ b/third_party/blink/web_tests/accessibility/aria-multiselect-state.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="grid" id="grid"></div>
 <div role="treegrid" id="treegrid"></div>
@@ -22,62 +21,62 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("grid");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable=false by default on grid");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("treegrid");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable=false by default on treegrid");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("tree");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable=false by default on tree");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("listbox");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable=false by default on listbox");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("tablist");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable=false by default on tablist");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("multi-grid");
     assert_equals(container.isMultiSelectable, true);
 }, "multiselectable can be set on grid");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("multi-treegrid");
     assert_equals(container.isMultiSelectable, true);
 }, "multiselectable can be set on treegrid");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("multi-tree");
     assert_equals(container.isMultiSelectable, true);
 }, "multiselectable can be set on tree");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("multi-listbox");
     assert_equals(container.isMultiSelectable, true);
 }, "multiselectable can be set on listbox");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("multi-tablist");
     assert_equals(container.isMultiSelectable, true);
 }, "multiselectable can be set on tablist");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("radiogroup");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable cannot be set on radiogrup");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var container = axElementById("menu");
     assert_equals(container.isMultiSelectable, false);
 }, "multiselectable cannot be set on menu");
diff --git a/third_party/blink/web_tests/accessibility/aria-option-role.html b/third_party/blink/web_tests/accessibility/aria-option-role.html
index 98d3afb8..9d35a251 100644
--- a/third_party/blink/web_tests/accessibility/aria-option-role.html
+++ b/third_party/blink/web_tests/accessibility/aria-option-role.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -13,7 +12,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var body = document.getElementById("body");
     body.focus();
 
diff --git a/third_party/blink/web_tests/accessibility/aria-owns-dynamic-changes.html b/third_party/blink/web_tests/accessibility/aria-owns-dynamic-changes.html
index 3ade788..aa9b6bc 100644
--- a/third_party/blink/web_tests/accessibility/aria-owns-dynamic-changes.html
+++ b/third_party/blink/web_tests/accessibility/aria-owns-dynamic-changes.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <ul id="future_parent" aria-owns="future_child"></ul>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFutureParent = accessibilityController.accessibleElementById("future_parent");
     assert_equals(axFutureParent.childrenCount, 0);
     var futureParent = document.getElementById("future_parent");
@@ -48,7 +47,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var source1 = document.getElementById("source1");
   var target = document.getElementById("target");
   var axSource1 = accessibilityController.accessibleElementById("source1");
@@ -73,7 +72,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axLogicalParent = accessibilityController.accessibleElementById("logical_parent");
 
diff --git a/third_party/blink/web_tests/accessibility/aria-owns-grid.html b/third_party/blink/web_tests/accessibility/aria-owns-grid.html
index 5d50a4e..f2d3b31 100644
--- a/third_party/blink/web_tests/accessibility/aria-owns-grid.html
+++ b/third_party/blink/web_tests/accessibility/aria-owns-grid.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <div id="grid" role="grid" aria-owns="row1 row2"></div>
@@ -18,7 +17,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axGrid = accessibilityController.accessibleElementById("grid");
     assert_equals(axGrid.role, "AXRole: AXGrid");
     assert_equals(axGrid.columnCount, 3);
diff --git a/third_party/blink/web_tests/accessibility/aria-owns-ignores-leafs.html b/third_party/blink/web_tests/accessibility/aria-owns-ignores-leafs.html
index eec1fe02..6933c0e9 100644
--- a/third_party/blink/web_tests/accessibility/aria-owns-ignores-leafs.html
+++ b/third_party/blink/web_tests/accessibility/aria-owns-ignores-leafs.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container" id="container1" role="group" aria-label="Container">
     <div id="heading1" role="heading" aria-owns="menu1">heading</div>
@@ -9,7 +8,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     // No problem with using aria-owns to put a menu inside a heading.
     var axHeading1 = accessibilityController.accessibleElementById("heading1");
     assert_equals(axHeading1.name, "heading");
@@ -25,7 +24,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     // A button can't have children, so we should ignore aria-owns.
     var axButton2 = accessibilityController.accessibleElementById("button2");
@@ -49,7 +48,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axTextfield3 = accessibilityController.accessibleElementById("textfield3");
     assert_equals(axTextfield3.name, "textfield");
@@ -70,7 +69,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axTextfield4 = accessibilityController.accessibleElementById("textfield4");
     assert_equals(axTextfield4.name, "textfield");
@@ -89,7 +88,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axTextfield5 = accessibilityController.accessibleElementById("textfield5");
     assert_equals(axTextfield5.name, "textfield");
diff --git a/third_party/blink/web_tests/accessibility/aria-owns-sends-notification.html b/third_party/blink/web_tests/accessibility/aria-owns-sends-notification.html
index e196067..3d85387 100644
--- a/third_party/blink/web_tests/accessibility/aria-owns-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/aria-owns-sends-notification.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container1">
     <ul id="future_parent" aria-owns="future_child"></ul>
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     var axFutureParent = accessibilityController.accessibleElementById("future_parent");
     assert_equals(axFutureParent.childrenCount, 0);
     var listener = function(notification) {
@@ -35,7 +34,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     window.setTimeout(t.step_func(() => {
         assert_unreached("Did not receive all expected notifications");
     }), 500);
@@ -64,7 +63,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     window.setTimeout(t.step_func(() => {
         assert_unreached("Did not receive all expected notifications");
     }), 500);
diff --git a/third_party/blink/web_tests/accessibility/aria-owns.html b/third_party/blink/web_tests/accessibility/aria-owns.html
index 5b8da07..bb028d38f 100644
--- a/third_party/blink/web_tests/accessibility/aria-owns.html
+++ b/third_party/blink/web_tests/accessibility/aria-owns.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <ul id="list1" role="listbox" aria-owns="item3">
@@ -15,7 +14,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axList1 = accessibilityController.accessibleElementById("list1");
     console.log('axList1: ' + axList1);
@@ -43,7 +42,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axParent1 = accessibilityController.accessibleElementById("parent1");
     var axParent2 = accessibilityController.accessibleElementById("parent2");
@@ -75,7 +74,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     // Make sure we can walk the whole tree.
     accessibilityController.accessibleElementById("dummy");
@@ -103,7 +102,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axRealParent = accessibilityController.accessibleElementById("real-parent");
     var axOwnsSelf  = accessibilityController.accessibleElementById("owns-self");
@@ -120,7 +119,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axYancy = accessibilityController.accessibleElementById("yancy");
     var axFry = accessibilityController.accessibleElementById("fry");
@@ -141,7 +140,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axLogicalParent = accessibilityController.accessibleElementById("logical_parent");
 
@@ -159,7 +158,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axDeadParent = accessibilityController.accessibleElementById(
         "dead_parent");
diff --git a/third_party/blink/web_tests/accessibility/aria-reflection.html b/third_party/blink/web_tests/accessibility/aria-reflection.html
index f8bc645b..e1e58c0 100644
--- a/third_party/blink/web_tests/accessibility/aria-reflection.html
+++ b/third_party/blink/web_tests/accessibility/aria-reflection.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="role" role="button"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("role");
     assert_equals(element.role, "button");
     element.role = "checkbox";
@@ -17,7 +16,7 @@
 <div id="activedescendant" aria-activedescendant="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("activedescendant");
     assert_equals(element.ariaActiveDescendant, "x");
     element.ariaActiveDescendant = "y";
@@ -28,7 +27,7 @@
 <div id="atomic" aria-atomic="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("atomic");
     assert_equals(element.ariaAtomic, "true");
     element.ariaAtomic = "false";
@@ -39,7 +38,7 @@
 <div id="autocomplete" aria-autocomplete="list"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("autocomplete");
     assert_equals(element.ariaAutoComplete, "list");
     element.ariaAutoComplete = "inline";
@@ -50,7 +49,7 @@
 <div id="busy" aria-busy="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("busy");
     assert_equals(element.ariaBusy, "true");
     element.ariaBusy = "false";
@@ -61,7 +60,7 @@
 <div id="checked" aria-checked="mixed"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("checked");
     assert_equals(element.ariaChecked, "mixed");
     element.ariaChecked = "true";
@@ -72,7 +71,7 @@
 <div id="colcount" aria-colcount="5"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("colcount");
     assert_equals(element.ariaColCount, "5");
     element.ariaColCount = "6";
@@ -83,7 +82,7 @@
 <div id="colindex" aria-colindex="1"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("colindex");
     assert_equals(element.ariaColIndex, "1");
     element.ariaColIndex = "2";
@@ -94,7 +93,7 @@
 <div id="colspan" aria-colspan="2"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("colspan");
     assert_equals(element.ariaColSpan, "2");
     element.ariaColSpan = "3";
@@ -105,7 +104,7 @@
 <div id="controls" aria-controls="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("controls");
     assert_equals(element.ariaControls, "x");
     element.ariaControls = "y";
@@ -116,7 +115,7 @@
 <div id="current" aria-current="page"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("current");
     assert_equals(element.ariaCurrent, "page");
     element.ariaCurrent = "step";
@@ -127,7 +126,7 @@
 <div id="describedby" aria-describedby="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("describedby");
     assert_equals(element.ariaDescribedBy, "x");
     element.ariaDescribedBy = "y";
@@ -138,7 +137,7 @@
 <div id="details" aria-details="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("details");
     assert_equals(element.ariaDetails, "x");
     element.ariaDetails = "y";
@@ -149,7 +148,7 @@
 <div id="disabled" aria-disabled="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("disabled");
     assert_equals(element.ariaDisabled, "true");
     element.ariaDisabled = "false";
@@ -160,7 +159,7 @@
 <div id="errormessage" aria-errormessage="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("errormessage");
     assert_equals(element.ariaErrorMessage, "x");
     element.ariaErrorMessage = "y";
@@ -171,7 +170,7 @@
 <div id="expanded" aria-expanded="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("expanded");
     assert_equals(element.ariaExpanded, "true");
     element.ariaExpanded = "false";
@@ -182,7 +181,7 @@
 <div id="flowto" aria-flowto="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("flowto");
     assert_equals(element.ariaFlowTo, "x");
     element.ariaFlowTo = "y";
@@ -193,7 +192,7 @@
 <div id="haspopup" aria-haspopup="menu"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("haspopup");
     assert_equals(element.ariaHasPopup, "menu");
     element.ariaHasPopup = "listbox";
@@ -204,7 +203,7 @@
 <div id="hidden" aria-hidden="true" tabindex="-1"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("hidden");
     assert_equals(element.ariaHidden, "true");
     element.ariaHidden = "false";
@@ -215,7 +214,7 @@
 <div id="keyshortcuts" aria-keyshortcuts="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("keyshortcuts");
     assert_equals(element.ariaKeyShortcuts, "x");
     element.ariaKeyShortcuts = "y";
@@ -226,7 +225,7 @@
 <div id="label" aria-label="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("label");
     assert_equals(element.ariaLabel, "x");
     element.ariaLabel = "y";
@@ -237,7 +236,7 @@
 <div id="labelledby" aria-labelledby="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("labelledby");
     assert_equals(element.ariaLabelledBy, "x");
     element.ariaLabelledBy = "y";
@@ -248,7 +247,7 @@
 <div id="level" aria-level="1"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("level");
     assert_equals(element.ariaLevel, "1");
     element.ariaLevel = "2";
@@ -259,7 +258,7 @@
 <div id="live" aria-live="polite"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("live");
     assert_equals(element.ariaLive, "polite");
     element.ariaLive = "assertive";
@@ -270,7 +269,7 @@
 <div id="modal" aria-modal="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("modal");
     assert_equals(element.ariaModal, "true");
     element.ariaModal = "false";
@@ -281,7 +280,7 @@
 <div id="multiline" aria-multiline="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("multiline");
     assert_equals(element.ariaMultiLine, "true");
     element.ariaMultiLine = "false";
@@ -292,7 +291,7 @@
 <div id="multiselectable" aria-multiselectable="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("multiselectable");
     assert_equals(element.ariaMultiSelectable, "true");
     element.ariaMultiSelectable = "false";
@@ -303,7 +302,7 @@
 <div id="orientation" aria-orientation="vertical"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("orientation");
     assert_equals(element.ariaOrientation, "vertical");
     element.ariaOrientation = "horizontal";
@@ -314,7 +313,7 @@
 <div id="owns" aria-owns="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("owns");
     assert_equals(element.ariaOwns, "x");
     element.ariaOwns = "y";
@@ -325,7 +324,7 @@
 <div id="placeholder" aria-placeholder="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("placeholder");
     assert_equals(element.ariaPlaceholder, "x");
     element.ariaPlaceholder = "y";
@@ -336,7 +335,7 @@
 <div id="posinset" aria-posinset="10"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("posinset");
     assert_equals(element.ariaPosInSet, "10");
     element.ariaPosInSet = "11";
@@ -347,7 +346,7 @@
 <button id="pressed" aria-pressed="true"></button>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("pressed");
     assert_equals(element.ariaPressed, "true");
     element.ariaPressed = "false";
@@ -358,7 +357,7 @@
 <div id="readonly" aria-readonly="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("readonly");
     assert_equals(element.ariaReadOnly, "true");
     element.ariaReadOnly = "false";
@@ -369,7 +368,7 @@
 <div id="relevant" aria-relevant="text"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("relevant");
     assert_equals(element.ariaRelevant, "text");
     element.ariaRelevant = "removals";
@@ -380,7 +379,7 @@
 <div id="required" aria-required="true"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("required");
     assert_equals(element.ariaRequired, "true");
     element.ariaRequired = "false";
@@ -391,7 +390,7 @@
 <div id="roledescription" aria-roledescription="x"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("roledescription");
     assert_equals(element.ariaRoleDescription, "x");
     element.ariaRoleDescription = "y";
@@ -402,7 +401,7 @@
 <div id="rowcount" aria-rowcount="10"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("rowcount");
     assert_equals(element.ariaRowCount, "10");
     element.ariaRowCount = "11";
@@ -413,7 +412,7 @@
 <div id="rowindex" aria-rowindex="1"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("rowindex");
     assert_equals(element.ariaRowIndex, "1");
     element.ariaRowIndex = "2";
@@ -424,7 +423,7 @@
 <div id="rowspan" aria-rowspan="2"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("rowspan");
     assert_equals(element.ariaRowSpan, "2");
     element.ariaRowSpan = "3";
@@ -435,7 +434,7 @@
 <div id="setsize" aria-setsize="10"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("setsize");
     assert_equals(element.ariaSelected, "10");
     element.ariaSelected = "11";
@@ -446,7 +445,7 @@
 <div id="sort" aria-sort="descending"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("sort");
     assert_equals(element.ariaSort, "descending");
     element.ariaSort = "ascending";
@@ -457,7 +456,7 @@
 <div id="valuemax" aria-valuemax="99"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("valuemax");
     assert_equals(element.ariaValueMax, "99");
     element.ariaValueMax = "100";
@@ -468,7 +467,7 @@
 <div id="valuemin" aria-valuemin="3"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("valuemin");
     assert_equals(element.ariaValueMin, "3");
     element.ariaValueMin = "2";
@@ -479,7 +478,7 @@
 <div id="valuenow" aria-valuenow="50"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("valuenow");
     assert_equals(element.ariaValueNow, "50");
     element.ariaValueNow = "51";
@@ -490,7 +489,7 @@
 <div id="valuetext" aria-valuetext="50%"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var element = document.getElementById("valuetext");
     assert_equals(element.ariaValueText, "50%");
     element.ariaValueText = "51%";
diff --git a/third_party/blink/web_tests/accessibility/aria-relations-should-ignore-hidden-targets.html b/third_party/blink/web_tests/accessibility/aria-relations-should-ignore-hidden-targets.html
index e3fd1df..b387833 100644
--- a/third_party/blink/web_tests/accessibility/aria-relations-should-ignore-hidden-targets.html
+++ b/third_party/blink/web_tests/accessibility/aria-relations-should-ignore-hidden-targets.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <ul id="tablist" role="tablist">
@@ -11,7 +10,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTab = accessibilityController.accessibleElementById('tab');
     var panel = document.getElementById('panel');
     var axPanel = accessibilityController.accessibleElementById('panel');
@@ -49,7 +48,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axHeading = accessibilityController.accessibleElementById('headingWithFlowtos');
     var flowtoItems = document.getElementById('flowtoItems');
diff --git a/third_party/blink/web_tests/accessibility/aria-row-name.html b/third_party/blink/web_tests/accessibility/aria-row-name.html
index 1bb6c86..02e65c07 100644
--- a/third_party/blink/web_tests/accessibility/aria-row-name.html
+++ b/third_party/blink/web_tests/accessibility/aria-row-name.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <body id="body">
 <div role="treegrid">
   <div role="row" id="treegrid1-row1" tabindex="-1">
@@ -28,17 +27,17 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axRow = axElementById("treegrid1-row1");
     assert_equals(axRow.name, 'row head data');
 }, "The row's name must concatenate the children if the row is focusable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axRow = axElementById("treegrid2-row1");
     assert_equals(axRow.name, 'row head data');
 }, "The row's name must concatenate the children if the parent has aria-activedescendant");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axRow = axElementById("treegrid3-row1");
     assert_equals(axRow.name, '');
 }, "The row's name must empty when the parent is not focusable and there is no other labelling markup (for performance reasons)");
diff --git a/third_party/blink/web_tests/accessibility/aria-setsize-posinset.html b/third_party/blink/web_tests/accessibility/aria-setsize-posinset.html
index 1c90e4c..60e964b 100644
--- a/third_party/blink/web_tests/accessibility/aria-setsize-posinset.html
+++ b/third_party/blink/web_tests/accessibility/aria-setsize-posinset.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -13,7 +12,7 @@
 </div>
 
 <script>
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var list = accessibilityController.accessibleElementById("list");
     assert_equals(list.childAtIndex(0).posInSet,3);
     assert_equals(list.childAtIndex(1).posInSet,5);
@@ -30,7 +29,7 @@
 </form>
 
 <script>
-  test_after_layout_and_paint(function(t){
+  test(function(t){
     var form = accessibilityController.accessibleElementById("form");
     // aria-posinset and aria-setSize can be implicitly caluclated for input type of radio.
     assert_equals(form.childAtIndex(0).posInSet,1);
@@ -48,7 +47,7 @@
 </div>
 
 <script>
-  test_after_layout_and_paint(function(t){
+  test(function(t){
     var radio1 = accessibilityController.accessibleElementById("radio1");
     assert_equals(radio1.posInSet,2);
     assert_equals(radio1.setSize,3);
diff --git a/third_party/blink/web_tests/accessibility/aria-tab-roles.html b/third_party/blink/web_tests/accessibility/aria-tab-roles.html
index 0ed8119..3a98f96 100644
--- a/third_party/blink/web_tests/accessibility/aria-tab-roles.html
+++ b/third_party/blink/web_tests/accessibility/aria-tab-roles.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -18,7 +17,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var body = document.getElementById("body");
     body.focus();
 
diff --git a/third_party/blink/web_tests/accessibility/aria-text-role.html b/third_party/blink/web_tests/accessibility/aria-text-role.html
index 186ab44..ca695f2 100644
--- a/third_party/blink/web_tests/accessibility/aria-text-role.html
+++ b/third_party/blink/web_tests/accessibility/aria-text-role.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -15,7 +14,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     document.getElementById("textrole").focus();
     var textrole = accessibilityController.focusedElement;
     assert_equals(textrole.role, 'AXRole: AXStaticText');
diff --git a/third_party/blink/web_tests/accessibility/aria-toggle-button-with-title.html b/third_party/blink/web_tests/accessibility/aria-toggle-button-with-title.html
index aacc5a6..3790bcbe 100644
--- a/third_party/blink/web_tests/accessibility/aria-toggle-button-with-title.html
+++ b/third_party/blink/web_tests/accessibility/aria-toggle-button-with-title.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -16,7 +15,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     document.getElementById("tbutton").focus();
     tbutton1 = accessibilityController.focusedElement;
     assert_equals(tbutton1.role, "AXRole: AXToggleButton");
diff --git a/third_party/blink/web_tests/accessibility/aria-treeitem-checkable.html b/third_party/blink/web_tests/accessibility/aria-treeitem-checkable.html
index 62df126..9caf2494 100644
--- a/third_party/blink/web_tests/accessibility/aria-treeitem-checkable.html
+++ b/third_party/blink/web_tests/accessibility/aria-treeitem-checkable.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="treeitem" id="treeitem-uncheckable">Uncheckable</div>
 <div role="treeitem" id="treeitem-false" aria-checked="false">Not checked</div>
@@ -14,22 +13,22 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var treeItem = axElementById("treeitem-uncheckable");
     assert_equals(treeItem.checked, "");
 }, "A tree item with no aria-checked has no checked property");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var treeItem = axElementById("treeitem-false");
     assert_equals(treeItem.checked, "false");
 }, "A tree item with aria-checked=false has checked=false");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var treeItem = axElementById("treeitem-true");
     assert_equals(treeItem.checked, "true");
 }, "A tree item with aria-checked=true has checked=true");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var treeItem = axElementById("treeitem-mixed");
     assert_equals(treeItem.checked, "mixed");
 }, "A tree item with aria-checked=mixed has checked=mixed");
diff --git a/third_party/blink/web_tests/accessibility/aria1.1-combo-box-with-delay.html b/third_party/blink/web_tests/accessibility/aria1.1-combo-box-with-delay.html
index 51489b2..d07e2a3 100644
--- a/third_party/blink/web_tests/accessibility/aria1.1-combo-box-with-delay.html
+++ b/third_party/blink/web_tests/accessibility/aria1.1-combo-box-with-delay.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="main">
   <div id="comboBox"
@@ -30,7 +29,7 @@
     document.querySelector('ul').style.display = 'block';
   });
 
-  async_test_after_layout_and_paint((t) => {
+  async_test((t) => {
     t.step_timeout(() => {
       assert_true(window.comboBoxInput.isFocused, 'Combo box should be focused.');
       let state1 = accessibilityController.accessibleElementById('state1');
@@ -48,7 +47,7 @@
     }, 0);
   }, 'An option with an activedescendant pointing to it is selected.');
 
-  async_test_after_layout_and_paint((t) => {
+  async_test((t) => {
     t.step_timeout(() => {
       document.querySelector('input').setAttribute('aria-activedescendant', 'state1');
 
diff --git a/third_party/blink/web_tests/accessibility/aria1.1-combo-box.html b/third_party/blink/web_tests/accessibility/aria1.1-combo-box.html
index f711aa79..9e5d3e2 100644
--- a/third_party/blink/web_tests/accessibility/aria1.1-combo-box.html
+++ b/third_party/blink/web_tests/accessibility/aria1.1-combo-box.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="comboBox"
     role="combobox"
@@ -26,7 +25,7 @@
     document.querySelector('input').focus();
   });
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     assert_true(window.comboBoxInput.isFocused, 'Combo box should be focused.');
     let state1 = accessibilityController.accessibleElementById('state1');
     assert_false(state1.isSelected, 'State1 should not be selected.');
@@ -40,7 +39,7 @@
     assert_false(state2.isFocused, 'State2 should not be focused.');
   }, 'An option with an activedescendant pointing to it is selected.');
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     document.querySelector('input').setAttribute('aria-activedescendant', 'state1');
 
     assert_true(window.comboBoxInput.isFocused, 'Combo box should be focused.');
diff --git a/third_party/blink/web_tests/accessibility/background-color.html b/third_party/blink/web_tests/accessibility/background-color.html
index 9ad3fef..4318d16 100644
--- a/third_party/blink/web_tests/accessibility/background-color.html
+++ b/third_party/blink/web_tests/accessibility/background-color.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!-- The default document base color is white. -->
 <div id="default">Document base color.</div>
@@ -27,35 +26,35 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function()
+test(function()
 {
     var axDefault = accessibilityController.accessibleElementById('default');
     assert_equals(axDefault.backgroundColor, 0xffffffff);  // White
     document.getElementById('default').style.display = 'none';
 }, 'Ensures that the document base color is exposed.');
 
-test_after_layout_and_paint(function()
+test(function()
 {
     var axTransparent = accessibilityController.accessibleElementById('transparent');
     assert_equals(axTransparent.backgroundColor, 0xff0000ff);  // Blue
     document.getElementById('transparent').style.display = 'none';
 }, 'Ensures that the color of the parent is exposed if the text background is transparent.');
 
-test_after_layout_and_paint(function()
+test(function()
 {
     var axObscuring = accessibilityController.accessibleElementById('obscuring-background');
     assert_equals(axObscuring.backgroundColor, 0xff008000);  // Green
     document.getElementById('obscuring-background').style.display = 'none';
 }, 'Ensures that the color of the parent is the only one exposed if it is not transparent.');
 
-test_after_layout_and_paint(function()
+test(function()
 {
     var axBlended = accessibilityController.accessibleElementById('blended-default');
     assert_equals(axBlended.backgroundColor, 0xffbfbfbf);  // Lite gray
     document.getElementById('blended-default').style.display = 'none';
 }, 'Ensures that semi-transparent text background color is blended with document base color.');
 
-test_after_layout_and_paint(function()
+test(function()
 {
     var axBlended = accessibilityController.accessibleElementById('blended-background');
     assert_equals(axBlended.backgroundColor, 0xff9f9f9f);  // Medium gray
diff --git a/third_party/blink/web_tests/accessibility/block-in-inline.html b/third_party/blink/web_tests/accessibility/block-in-inline.html
index a231471..c0e4d4f 100644
--- a/third_party/blink/web_tests/accessibility/block-in-inline.html
+++ b/third_party/blink/web_tests/accessibility/block-in-inline.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <a id="link" href="#">
   <b id="inline" role="group">Inline</b>
@@ -18,7 +17,7 @@
     return IsAncestorOf(ancestor, parent);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLink = accessibilityController.accessibleElementById("link");
     var axInline = accessibilityController.accessibleElementById("inline");
     var axBlock = accessibilityController.accessibleElementById("block");
diff --git a/third_party/blink/web_tests/accessibility/bounds-calc.html b/third_party/blink/web_tests/accessibility/bounds-calc.html
index 7b7ce23..533ccb0 100644
--- a/third_party/blink/web_tests/accessibility/bounds-calc.html
+++ b/third_party/blink/web_tests/accessibility/bounds-calc.html
@@ -102,7 +102,7 @@
     assert_equals(axObject.hasNonIdentityTransform(), expected, id + " hasNonIdentityTransform");
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assertDOMRectSameAsAXRect("input");
     assertDOMRectSameAsAXRect("checkbox");
     assertDOMRectSameAsAXRect("radio");
@@ -146,7 +146,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assertHasTransform("container2", false);
 
     assertHasTransform("div2", false);
@@ -163,17 +163,18 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     document.getElementById("container3").scrollTop = 200;
+    runAfterLayoutAndPaint(t.step_func_done(() => {
+        assertHasTransform("container3", false);
 
-    assertHasTransform("container3", false);
-
-    assertHasTransform("div3", false);
-    assertDOMRectSameAsAXRect("div3");
-    assertOffsetContainerIs("div3", "container3");
-    assertHasTransform("input3", false);
-    assertDOMRectSameAsAXRect("input3");
-    assertOffsetContainerIs("input3", "container3");
+        assertHasTransform("div3", false);
+        assertDOMRectSameAsAXRect("div3");
+        assertOffsetContainerIs("div3", "container3");
+        assertHasTransform("input3", false);
+        assertDOMRectSameAsAXRect("input3");
+        assertOffsetContainerIs("input3", "container3");
+    }));
 }, "Test objects inside of scrolled container");
 </script>
 
@@ -182,7 +183,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assertHasTransform("container4", true);
 
     assertHasTransform("div4", false);
@@ -204,9 +205,11 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     document.getElementById("scroll5").scrollTop = 40;
-    assertDOMRectSameAsAXRect("div5");
+    runAfterLayoutAndPaint(t.step_func_done(() => {
+      assertDOMRectSameAsAXRect("div5");
+    }));
 }, "Test object inside of several nested containers with transforms and scrolling.");
 </script>
 
@@ -223,7 +226,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     // This test uses aria-owns to rearrange children between ancestors,
     // to ensure that an object's offset container is not a node that's an
     // ancestor in the accessibility tree but not in the layout tree.
@@ -256,7 +259,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assertHasTransform("container7", false);
     assertHasTransform("span7", false);
     assertDOMRectSameAsAXRect("span7");
@@ -269,7 +272,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assertStaticTextChildDOMRectSameAsAXRect("span8", 0);
 }, "Test node that's inline and relative-positioned");
 </script>
diff --git a/third_party/blink/web_tests/accessibility/button-title-uses-inner-img-alt.html b/third_party/blink/web_tests/accessibility/button-title-uses-inner-img-alt.html
index de53d57..26fc45c 100644
--- a/third_party/blink/web_tests/accessibility/button-title-uses-inner-img-alt.html
+++ b/third_party/blink/web_tests/accessibility/button-title-uses-inner-img-alt.html
@@ -3,7 +3,6 @@
 <body>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <style>
 button {
@@ -14,7 +13,7 @@
 <button id="cake">Button with image of <img alt="cake" src="resources/cake.png" width="40px"></button>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var button = document.getElementById('cake');
     button.focus();
     assert_equals(document.activeElement == button, true);
diff --git a/third_party/blink/web_tests/accessibility/calling-accessibility-methods-with-pending-layout-causes-crash.html b/third_party/blink/web_tests/accessibility/calling-accessibility-methods-with-pending-layout-causes-crash.html
index 5fc93a8b..e51acb7 100644
--- a/third_party/blink/web_tests/accessibility/calling-accessibility-methods-with-pending-layout-causes-crash.html
+++ b/third_party/blink/web_tests/accessibility/calling-accessibility-methods-with-pending-layout-causes-crash.html
@@ -2,12 +2,11 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 <a href="#" id="link">
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var link = document.getElementById("link");
     link.focus();
     link.style.display = "block";
diff --git a/third_party/blink/web_tests/accessibility/canvas-fallback-content-2.html b/third_party/blink/web_tests/accessibility/canvas-fallback-content-2.html
index 60762ee..f58225b44 100644
--- a/third_party/blink/web_tests/accessibility/canvas-fallback-content-2.html
+++ b/third_party/blink/web_tests/accessibility/canvas-fallback-content-2.html
@@ -3,7 +3,6 @@
 <body>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div>
   <a id="link1" href="#">Link</a>
@@ -79,7 +78,7 @@
     assert_equals(axElement2.maxValue, axElement1.maxValue);
 }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     check("link1", "link2");
     check("button1", "button2");
     check("labeled-button1", "labeled-button2");
diff --git a/third_party/blink/web_tests/accessibility/canvas-fallback-content.html b/third_party/blink/web_tests/accessibility/canvas-fallback-content.html
index 0c04b3ff..6b3c61d 100644
--- a/third_party/blink/web_tests/accessibility/canvas-fallback-content.html
+++ b/third_party/blink/web_tests/accessibility/canvas-fallback-content.html
@@ -3,7 +3,6 @@
 <body>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <style>
 myelement {
@@ -49,7 +48,7 @@
       assert_equals(axElement.role, expectedRole, id);
   }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var comboBoxRole = (testRunner.platformName == "gtk" || testRunner.platformName == "efl") ? "AXRole: AXComboBox" : "AXRole: AXPopUpButton";
 
     // Check rendered controls.
diff --git a/third_party/blink/web_tests/accessibility/canvas-select-row.html b/third_party/blink/web_tests/accessibility/canvas-select-row.html
index a10bfc7..5b470e0a 100644
--- a/third_party/blink/web_tests/accessibility/canvas-select-row.html
+++ b/third_party/blink/web_tests/accessibility/canvas-select-row.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="grid">
   <div role="row" id="row0">
@@ -17,7 +16,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var row = document.getElementById("row0");
     var cell = new AccessibleNode();
     cell.role = "gridcell";
@@ -27,7 +26,7 @@
     assert_equals(axChild.role, "AXRole: AXCell");
 }, "An ARIA cell without a layout object does not crash");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("row1");
     assert_equals(ax.role, "AXRole: AXRow");
 }, "An ARIA row in a select in a canvas does not crash");
diff --git a/third_party/blink/web_tests/accessibility/children-changed-in-canvas.html b/third_party/blink/web_tests/accessibility/children-changed-in-canvas.html
index e5a12127..33fda2d 100644
--- a/third_party/blink/web_tests/accessibility/children-changed-in-canvas.html
+++ b/third_party/blink/web_tests/accessibility/children-changed-in-canvas.html
@@ -8,7 +8,7 @@
 </canvas>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
     var container = document.getElementById("container");
     var axContainer = accessibilityController.accessibleElementById("container");
     assert_equals(axContainer.childrenCount, 3, "Initial");
diff --git a/third_party/blink/web_tests/accessibility/click-event.html b/third_party/blink/web_tests/accessibility/click-event.html
index 9d3d2bb..3e50501 100644
--- a/third_party/blink/web_tests/accessibility/click-event.html
+++ b/third_party/blink/web_tests/accessibility/click-event.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="wrapper1">
     <button id="button1">Button</button>
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button1");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -28,7 +27,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button2");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -48,7 +47,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button3");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -68,7 +67,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button4");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -86,7 +85,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button5");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -116,7 +115,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button6");
     axButton.addNotificationListener(t.step_func(function(notification) {
@@ -147,7 +146,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axButton = accessibilityController.accessibleElementById("button7");
     axButton.addNotificationListener(t.step_func(function(notification) {
diff --git a/third_party/blink/web_tests/accessibility/color-changed.html b/third_party/blink/web_tests/accessibility/color-changed.html
index 39b7b99..80ff246 100644
--- a/third_party/blink/web_tests/accessibility/color-changed.html
+++ b/third_party/blink/web_tests/accessibility/color-changed.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="blue" style="color: blue">Blue foreground.</p>
 
@@ -9,7 +8,7 @@
 const kBlueColor = 0xFF;
 const kGreenColor = 0x8000;
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     let blue = document.getElementById('blue');
     let axBlue = accessibilityController.accessibleElementById('blue');
     assert_equals(axBlue.color, kBlueColor);
diff --git a/third_party/blink/web_tests/accessibility/computed-text-with-height-0.html b/third_party/blink/web_tests/accessibility/computed-text-with-height-0.html
index c81b384..67608b8 100644
--- a/third_party/blink/web_tests/accessibility/computed-text-with-height-0.html
+++ b/third_party/blink/web_tests/accessibility/computed-text-with-height-0.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <a id="button" href="#" role="button"><span style="height:0px;position:absolute;overflow:hidden">Friend Requests:</span><span>0</span></a>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axButton = accessibilityController.accessibleElementById("button");
     assert_equals(axButton.name, "Friend Requests: 0");
     assert_equals(axButton.name, "Friend Requests: 0");
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html b/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
index 7d2d059..b424e38e 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="main" role="main">
 
@@ -25,7 +24,7 @@
 </div>
 
 <script>
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let mainAccessible = accessibilityController.accessibleElementById("main");
 
@@ -35,7 +34,7 @@
         assert_equals(mainAccessible.selectionFocusOffset, -1);
     }, "Initially there should be no selection under the main object.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let rootAccessible = accessibilityController.rootElement;
 
@@ -45,7 +44,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, -1);
     }, "Initially there should be no selection on the root object.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let textbox = document.getElementById("contenteditable-textbox");
         textbox.focus();
@@ -57,7 +56,7 @@
         assert_equals(textboxAccessible.selectionFocusOffset, 0);
     }, "Moving the focus to an ARIA textbox should place the caret at its beginning.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let selection = window.getSelection();
         let selectionRange = document.createRange();
@@ -89,7 +88,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 1);
     }, "Setting a new caret position in the ARIA textbox should be reflected in the accessibility APIs.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let selection = window.getSelection();
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
@@ -114,7 +113,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, -1);
     }, "Removing the selection should remove the caret completely.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let selection = window.getSelection();
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
@@ -141,7 +140,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 2);
     }, "Positioning the caret using the accessibility API instead of the DOM should work.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let line2Accessible = accessibilityController.accessibleElementById("contenteditable-line2");
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
@@ -165,7 +164,7 @@
         assert_equals(mainAccessible.selectionFocusOffset, 0);
     }, "Moving the focus into a textarea should remove the caret from the ARIA textbox.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         document.getElementById("contenteditable-line2").focus();
         let rootAccessible = accessibilityController.rootElement;
@@ -182,7 +181,7 @@
         assert_equals(line2Accessible.selectionFocusOffset, 0);
     }, "Standard text fields start with the caret at the beginning of their contents.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let line2 = document.getElementById("contenteditable-line2");
         line2.focus();
@@ -196,7 +195,7 @@
         assert_equals(line2Accessible.selectionFocusOffset, 3);
     }, "Setting a new caret position in the textarea should be exposed in the accessibility APIs.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
         let mainAccessible = accessibilityController.accessibleElementById("main");
@@ -220,7 +219,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 3);
     }, "Offsets in text fields should be reported from the beginning of the field and not from the top of the container.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         var line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
         var line2Accessible = accessibilityController.accessibleElementById("contenteditable-line2");
@@ -235,7 +234,7 @@
         assert_equals(line2Accessible.selectionFocusOffset, 3);
     }, "The caret position should be retrievable from any object.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         const selection = window.getSelection();
         const selectionRange = document.createRange();
@@ -296,7 +295,7 @@
 
     }, "Test moving the caret across two paragraphs by re-creating the selection.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         const selection = window.getSelection();
         const selectionRange = document.createRange();
@@ -345,7 +344,7 @@
         
     }, "Test moving the caret across two paragraphs by modifying the existing selection.");
 
-    test_after_layout_and_paint(function()
+    test(function()
     {
         const selection = window.getSelection();
         const selectionRange = document.createRange();
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-notifications.html b/third_party/blink/web_tests/accessibility/contenteditable-notifications.html
index d77da6ad..c18c32e 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-notifications.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-notifications.html
@@ -4,14 +4,13 @@
 <script src="../resources/accessibility-helper.js"></script>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
 <div id="textbox" contentEditable="true">First<p>Second</p></div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     // TODO(aboxhall): No longer necessary when updates are processed after layout.
     // Ensure entire a11y tree has already been seen.
     traverseAccessibilityTree(accessibilityController.rootElement);
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-on-aria-hidden-body.html b/third_party/blink/web_tests/accessibility/contenteditable-on-aria-hidden-body.html
index fa8f640..25b8dbe 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-on-aria-hidden-body.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-on-aria-hidden-body.html
@@ -1,13 +1,12 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <body contenteditable aria-hidden="true">
   &nbsp;
 
   <script>
-  test_after_layout_and_paint(function() {
+  test(function() {
     var axDocument = accessibilityController.rootElement;
     assert_equals(axDocument.childrenCount, 1);
 
@@ -15,7 +14,7 @@
     assert_false(axDocument.isRichlyEditable);
   }, 'The document should not be marked editable because the body is aria-hidden.');
 
-  test_after_layout_and_paint(function() {
+  test(function() {
     document.body.setAttribute('aria-hidden', 'false');
     var axDocument = accessibilityController.rootElement;
     assert_equals(axDocument.childrenCount, 1);
diff --git a/third_party/blink/web_tests/accessibility/contenteditable-selection.html b/third_party/blink/web_tests/accessibility/contenteditable-selection.html
index e2e2e90..8e1ae85b 100644
--- a/third_party/blink/web_tests/accessibility/contenteditable-selection.html
+++ b/third_party/blink/web_tests/accessibility/contenteditable-selection.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="main" role="main">
 
@@ -18,7 +17,7 @@
 </div>
 
 <script>
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let selection = window.getSelection();
         let selectionRange = document.createRange();
@@ -54,7 +53,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 2);
     }, "Test selectNodeContents on an ARIA textbox.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let selection = window.getSelection();
         let selectionRange = document.createRange();
@@ -88,7 +87,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 2);
     }, "Test selectNodeContents on a contenteditable.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let selection = window.getSelection();
         let selectionRange = document.createRange();
@@ -121,7 +120,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 6);
     }, "The effects of the setStart and setEnd methods should be reflected in the accessibility API.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
         let rootAccessible = accessibilityController.rootElement;
@@ -143,7 +142,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 0);
     }, "Verify that changing the focus removes the selection.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
         let mainAccessible = accessibilityController.accessibleElementById("main");
@@ -174,7 +173,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, line2.textLength);
     }, "The effects of the textarea.setSelectionRange method should be reflected in the accessibility API.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
         let mainAccessible = accessibilityController.accessibleElementById("main");
@@ -205,7 +204,7 @@
         assert_equals(rootAccessible.selectionFocusOffset, 5);
     }, "Test the setSelectedTextRange accessibility API function.");
 
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         let selection = window.getSelection();
         let selectionRange = document.createRange();
diff --git a/third_party/blink/web_tests/accessibility/continuation.html b/third_party/blink/web_tests/accessibility/continuation.html
index a2e8009..f5a19ad6 100644
--- a/third_party/blink/web_tests/accessibility/continuation.html
+++ b/third_party/blink/web_tests/accessibility/continuation.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <b>
   <i>
@@ -10,7 +9,7 @@
     </div></i><div id="after">After</div></b>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axBefore = accessibilityController.accessibleElementById("before");
     assert_equals(axBefore.name, "Before");
     var axAfter = accessibilityController.accessibleElementById("after");
diff --git a/third_party/blink/web_tests/accessibility/continuation2.html b/third_party/blink/web_tests/accessibility/continuation2.html
index fb1062bcd..4025865 100644
--- a/third_party/blink/web_tests/accessibility/continuation2.html
+++ b/third_party/blink/web_tests/accessibility/continuation2.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <foo>
   <bar>
@@ -10,7 +9,7 @@
 </foo>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axBefore = accessibilityController.accessibleElementById("before");
     assert_equals(axBefore.childAtIndex(0).name, "Before");
     var axAfter = accessibilityController.accessibleElementById("after");
diff --git a/third_party/blink/web_tests/accessibility/continuation3.html b/third_party/blink/web_tests/accessibility/continuation3.html
index d128d6d65..a6e7ad9 100644
--- a/third_party/blink/web_tests/accessibility/continuation3.html
+++ b/third_party/blink/web_tests/accessibility/continuation3.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div>
   <span>
@@ -16,7 +15,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axBefore = accessibilityController.accessibleElementById("before");
     assert_equals(axBefore.childAtIndex(0).name, "Before");
     var axEver = accessibilityController.accessibleElementById("ever");
diff --git a/third_party/blink/web_tests/accessibility/continuation4.html b/third_party/blink/web_tests/accessibility/continuation4.html
index 213a8e5..d29905d 100644
--- a/third_party/blink/web_tests/accessibility/continuation4.html
+++ b/third_party/blink/web_tests/accessibility/continuation4.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="before">Before</div>
 <div><span><p id="after">After</p></span></div>
 
 <script>
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var axBefore = accessibilityController.accessibleElementById("before");
     assert_equals(axBefore.childAtIndex(0).name, "Before");
     var axAfter = accessibilityController.accessibleElementById("after");
diff --git a/third_party/blink/web_tests/accessibility/css-first-letter-children.html b/third_party/blink/web_tests/accessibility/css-first-letter-children.html
index 7fd7e71..9dca0399 100644
--- a/third_party/blink/web_tests/accessibility/css-first-letter-children.html
+++ b/third_party/blink/web_tests/accessibility/css-first-letter-children.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
 p::first-letter {
   font-size: 200%;
@@ -10,7 +9,7 @@
 <h1 id="heading">Test heading</h1>
 <p id="text">Test text</p>
 <script>
-test_after_layout_and_paint(function () {
+test(function () {
   if (!window.accessibilityController) {
     assert_unreached("This test requires accessibilityController.");
     return;
diff --git a/third_party/blink/web_tests/accessibility/default-language.html b/third_party/blink/web_tests/accessibility/default-language.html
index e87ec14..9757dfa 100644
--- a/third_party/blink/web_tests/accessibility/default-language.html
+++ b/third_party/blink/web_tests/accessibility/default-language.html
@@ -2,7 +2,6 @@
 <html>
   <script src="../resources/testharness.js"></script>
   <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
   <body id="body" role="main">
     <p id="paragraph">Some text.
@@ -17,7 +16,7 @@
       window.axSpan = accessibilityController.accessibleElementById('span');
     });
 
-    async_test_after_layout_and_paint((t) => {
+    async_test((t) => {
       assert_equals(window.axBody.language, 'AXLanguage: en-US',
           'The browser language has not been changed yet.');
 
diff --git a/third_party/blink/web_tests/accessibility/description-calc-aria-describedby.html b/third_party/blink/web_tests/accessibility/description-calc-aria-describedby.html
index e96b2c4..abb9be6 100644
--- a/third_party/blink/web_tests/accessibility/description-calc-aria-describedby.html
+++ b/third_party/blink/web_tests/accessibility/description-calc-aria-describedby.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <div id="div">Div Contents</div>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDiv = accessibilityController.accessibleElementById("div");
     assert_equals(axDiv.description, "");
 }, "A simple div should not have an accessible description.");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axSelf = accessibilityController.accessibleElementById("self");
     assert_equals(axSelf.name, "Contents of button");
     assert_equals(axSelf.nameFrom, "contents");
@@ -34,7 +33,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDescribedby = accessibilityController.accessibleElementById("describedby");
     assert_equals(axDescribedby.name, "Contents");
     assert_equals(axDescribedby.nameFrom, "contents");
@@ -50,7 +49,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDescribedby2 = accessibilityController.accessibleElementById("describedby2");
     assert_equals(axDescribedby2.name, "Contents");
     assert_equals(axDescribedby2.nameFrom, "contents");
@@ -67,7 +66,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDescribedby3 = accessibilityController.accessibleElementById("describedby3");
     assert_equals(axDescribedby3.name, "Contents");
     assert_equals(axDescribedby3.nameFrom, "contents");
@@ -84,7 +83,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDescribedby4 = accessibilityController.accessibleElementById("describedby4");
     assert_equals(axDescribedby4.name, "Contents");
     assert_equals(axDescribedby4.nameFrom, "contents");
@@ -100,7 +99,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDescribedby5 = accessibilityController.accessibleElementById("describedby5");
     assert_equals(axDescribedby5.name, "Contents");
     assert_equals(axDescribedby5.nameFrom, "contents");
@@ -117,7 +116,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     assert_equals(accessibilityController.accessibleElementById("description6"), undefined);
     var axDescribedby6 = accessibilityController.accessibleElementById("describedby6");
     assert_equals(axDescribedby6.name, "Contents");
@@ -135,7 +134,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     assert_equals(accessibilityController.accessibleElementById("description7"), undefined);
 var axDescribedby7 = accessibilityController.accessibleElementById("describedby7");
     assert_equals(axDescribedby7.name, "Contents");
diff --git a/third_party/blink/web_tests/accessibility/description-calc-inputs.html b/third_party/blink/web_tests/accessibility/description-calc-inputs.html
index a8f817a15..974975e 100644
--- a/third_party/blink/web_tests/accessibility/description-calc-inputs.html
+++ b/third_party/blink/web_tests/accessibility/description-calc-inputs.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <input id="text1" type="text" placeholder="Placeholder">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput1 = accessibilityController.accessibleElementById("text1");
     assert_equals(axTextInput1.name, "Placeholder");
     assert_equals(axTextInput1.nameFrom, "placeholder");
@@ -22,7 +21,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput1a = accessibilityController.accessibleElementById("text1a");
     assert_equals(axTextInput1a.name, "ARIA Placeholder");
     assert_equals(axTextInput1a.nameFrom, "placeholder");
@@ -36,7 +35,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput1b = accessibilityController.accessibleElementById("text1b");
     assert_equals(axTextInput1b.name, "Placeholder");
     assert_equals(axTextInput1b.nameFrom, "placeholder");
@@ -50,7 +49,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput2 = accessibilityController.accessibleElementById("text2");
     assert_equals(axTextInput2.name, "Label");
     assert_equals(axTextInput2.nameFrom, "attribute");
@@ -65,7 +64,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput2a = accessibilityController.accessibleElementById("text2a");
     assert_equals(axTextInput2a.name, "Label");
     assert_equals(axTextInput2a.nameFrom, "attribute");
@@ -80,7 +79,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput2b = accessibilityController.accessibleElementById("text2b");
     assert_equals(axTextInput2b.name, "Label");
     assert_equals(axTextInput2b.nameFrom, "attribute");
@@ -96,7 +95,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput3 = accessibilityController.accessibleElementById("text3");
     assert_equals(axTextInput3.name, "Label");
     assert_equals(axTextInput3.nameFrom, "attribute");
@@ -111,7 +110,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput4 = accessibilityController.accessibleElementById("text4");
     assert_equals(axTextInput4.name, "Title");
     assert_equals(axTextInput4.nameFrom, "title");
@@ -125,7 +124,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput5 = accessibilityController.accessibleElementById("text5");
     assert_equals(axTextInput5.name, "Label");
     assert_equals(axTextInput5.nameFrom, "attribute");
@@ -140,7 +139,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput6 = accessibilityController.accessibleElementById("text6");
     assert_equals(axTextInput6.name, "Label");
     assert_equals(axTextInput6.nameFrom, "attribute");
@@ -159,7 +158,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput7 = accessibilityController.accessibleElementById("text7");
     assert_equals(axTextInput7.name, "");
     assert_equals(axTextInput7.nameFrom, "");
diff --git a/third_party/blink/web_tests/accessibility/description-calc-native-markup-input-buttons.html b/third_party/blink/web_tests/accessibility/description-calc-native-markup-input-buttons.html
index 688656e..f10c170 100644
--- a/third_party/blink/web_tests/accessibility/description-calc-native-markup-input-buttons.html
+++ b/third_party/blink/web_tests/accessibility/description-calc-native-markup-input-buttons.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <input id="button1" type="button">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput1 = accessibilityController.accessibleElementById("button1");
     assert_equals(axButtonInput1.name, "");
     assert_equals(axButtonInput1.nameFrom, "");
@@ -22,7 +21,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput2 = accessibilityController.accessibleElementById("button2");
     assert_equals(axButtonInput2.name, "button-value2");
     assert_equals(axButtonInput2.nameFrom, "value");
@@ -36,7 +35,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput3 = accessibilityController.accessibleElementById("button3");
     assert_equals(axButtonInput3.name, "button-value3");
     assert_equals(axButtonInput3.nameFrom, "value");
@@ -50,7 +49,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton1 = accessibilityController.accessibleElementById("submit1");
     assert_equals(axSubmitButton1.name, "Submit");
     assert_equals(axSubmitButton1.nameFrom, "contents");
@@ -64,7 +63,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton2 = accessibilityController.accessibleElementById("submit2");
     assert_equals(axSubmitButton2.name, "submit-value2");
     assert_equals(axSubmitButton2.nameFrom, "value");
@@ -78,7 +77,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton3 = accessibilityController.accessibleElementById("submit3");
     assert_equals(axSubmitButton3.name, "Submit");
     assert_equals(axSubmitButton3.nameFrom, "contents");
@@ -92,7 +91,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton4 = accessibilityController.accessibleElementById("submit4");
     assert_equals(axSubmitButton4.name, "Label");
     assert_equals(axSubmitButton4.nameFrom, "attribute");
@@ -106,7 +105,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton5 = accessibilityController.accessibleElementById("submit5");
     assert_equals(axSubmitButton5.name, "Label");
     assert_equals(axSubmitButton5.nameFrom, "attribute");
diff --git a/third_party/blink/web_tests/accessibility/description-calc-summary.html b/third_party/blink/web_tests/accessibility/description-calc-summary.html
index 35e7fcb..a940817 100644
--- a/third_party/blink/web_tests/accessibility/description-calc-summary.html
+++ b/third_party/blink/web_tests/accessibility/description-calc-summary.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <details id="details1">
@@ -10,7 +9,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axDetails1 = accessibilityController.accessibleElementById("details1");
     var axSummary1 = axDetails1.childAtIndex(0);
     assert_equals(axSummary1.role, "AXRole: AXDisclosureTriangle");
@@ -29,7 +28,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary2 = accessibilityController.accessibleElementById("summary2");
     assert_equals(axSummary2.name, "summary2-contents");
     assert_equals(axSummary2.nameFrom, "contents");
@@ -46,7 +45,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary3 = accessibilityController.accessibleElementById("summary3");
     assert_equals(axSummary3.name, "summary3-aria-label");
     assert_equals(axSummary3.nameFrom, "attribute");
diff --git a/third_party/blink/web_tests/accessibility/description-calc-table-caption.html b/third_party/blink/web_tests/accessibility/description-calc-table-caption.html
index 0abaed3..eb3d347 100644
--- a/third_party/blink/web_tests/accessibility/description-calc-table-caption.html
+++ b/third_party/blink/web_tests/accessibility/description-calc-table-caption.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <!-- Note: border=1 overrides layout table heuristics -->
@@ -12,7 +11,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTable1 = accessibilityController.accessibleElementById("table1");
     assert_equals(axTable1.name, "Caption");
     assert_equals(axTable1.nameFrom, "caption");
@@ -30,7 +29,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTable2 = accessibilityController.accessibleElementById("table2");
     assert_equals(axTable2.name, "Label");
     assert_equals(axTable2.nameFrom, "attribute");
diff --git a/third_party/blink/web_tests/accessibility/disabled-controls.html b/third_party/blink/web_tests/accessibility/disabled-controls.html
index d8756c85..f5526d2 100644
--- a/third_party/blink/web_tests/accessibility/disabled-controls.html
+++ b/third_party/blink/web_tests/accessibility/disabled-controls.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container">
   <input type="text" id="input" readonly>
@@ -17,22 +16,22 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("input");
     assert_equals(ax.restriction, "readOnly");
 }, "A readonly input is readonly");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("textarea");
     assert_equals(ax.restriction, "disabled");
 }, "A textarea can be disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("range-in-fieldset");
     assert_equals(ax.restriction, "disabled");
 }, "A range in a disabled fieldset is disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("para-in-fieldset");
     assert_equals(ax.restriction, "none");
 }, "A paragraph in a disabled fieldset is not disabled");
diff --git a/third_party/blink/web_tests/accessibility/disabled-not-selectable.html b/third_party/blink/web_tests/accessibility/disabled-not-selectable.html
index 7a0c4de..8c5dc18 100644
--- a/third_party/blink/web_tests/accessibility/disabled-not-selectable.html
+++ b/third_party/blink/web_tests/accessibility/disabled-not-selectable.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="treeitem1" role="treeitem" tabindex="-1">X</div>
 <div id="option1" role="option" tabindex="-1">X</div>
@@ -27,117 +26,117 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("treeitem1");
     assert_equals(ax.restriction, "none");
 }, "Tree item enabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("option1");
     assert_equals(ax.restriction, "none");
 }, "Option enabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("tab1");
     assert_equals(ax.restriction, "none");
 }, "Tab enabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("gridcell1");
     assert_equals(ax.restriction, "none");
 }, "Grid cell enabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("checkbox1");
     assert_equals(ax.restriction, "none");
 }, "Check boxs enabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("treeitem1");
     assert_equals(ax.isSelectable, true);
 }, "Tree item selectable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("option1");
     assert_equals(ax.isSelectable, true);
 }, "Option selectable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("tab1");
     assert_equals(ax.isSelectable, true);
 }, "Tab selectable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("gridcell1");
     assert_equals(ax.isSelectable, true);
 }, "Grid cell selectable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("checkbox1");
     assert_equals(ax.isSelectable, false);
 }, "Check box not selectable because it is not a selectable role");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("treeitem2");
     assert_equals(ax.isSelected, true);
 }, "Tree item selected");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("option2");
     assert_equals(ax.isSelected, true);
 }, "Option selected");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("tab2");
     assert_equals(ax.isSelected, true);
 }, "Tab selected");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("gridcell2");
     assert_equals(ax.isSelected, true);
 }, "Grid cell selected");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("checkbox2");
     assert_equals(ax.isSelected, false);
 }, "Check box not selected because it is not a selectable role");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("treeitem3");
     assert_equals(ax.isSelectable, false);
 }, "Tree item not selectable if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("option3");
     assert_equals(ax.isSelectable, false);
 }, "Option not selectable if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("tab3");
     assert_equals(ax.isSelectable, false);
 }, "Tab not selectable if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("gridcell3");
     assert_equals(ax.isSelectable, false);
 }, "Grid cell not selectable if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("treeitem3");
     assert_equals(ax.isSelected, false);
 }, "Tree item not selected if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("option3");
     assert_equals(ax.isSelected, false);
 }, "Option not selected if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("tab3");
     assert_equals(ax.isSelected, false);
 }, "Tab not selected if disabled");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("gridcell3");
     assert_equals(ax.isSelected, false);
 }, "Grid cell not selected if disabled");
diff --git a/third_party/blink/web_tests/accessibility/display-none-change.html b/third_party/blink/web_tests/accessibility/display-none-change.html
index a414fbee..99bb50e5 100644
--- a/third_party/blink/web_tests/accessibility/display-none-change.html
+++ b/third_party/blink/web_tests/accessibility/display-none-change.html
@@ -1,13 +1,12 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <button id="button">Button</button>
 <label id="label" for="button" style="display:none" aria-label="AriaLabel">Label</label>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton = accessibilityController.accessibleElementById("button");
 
     // Get the AXObject for the label indirectly, even though it's display:none.
diff --git a/third_party/blink/web_tests/accessibility/document-element-display-none-crash.html b/third_party/blink/web_tests/accessibility/document-element-display-none-crash.html
index 1eb91fad..a977432 100644
--- a/third_party/blink/web_tests/accessibility/document-element-display-none-crash.html
+++ b/third_party/blink/web_tests/accessibility/document-element-display-none-crash.html
@@ -1,10 +1,9 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   document.documentElement.style.display = 'none';
   accessibilityController.rootElement.name;
   document.documentElement.style.display = 'block';
diff --git a/third_party/blink/web_tests/accessibility/draw-focus-if-needed.html b/third_party/blink/web_tests/accessibility/draw-focus-if-needed.html
index f8bac06..41d9bac 100644
--- a/third_party/blink/web_tests/accessibility/draw-focus-if-needed.html
+++ b/third_party/blink/web_tests/accessibility/draw-focus-if-needed.html
@@ -3,7 +3,6 @@
 <title>Canvas test: drawSystemFocusRing</title>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body style="padding: 0; margin: 0">
 <canvas id="canvas" class="output" width="300" height="350">
@@ -14,7 +13,7 @@
 </canvas>
 <div id="console"></div>
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     axButton1 = accessibilityController.accessibleElementById("button1");
     axContainer = accessibilityController.accessibleElementById("container");
     axButton2 = accessibilityController.accessibleElementById("button2");
diff --git a/third_party/blink/web_tests/accessibility/editable-anonymous-block.html b/third_party/blink/web_tests/accessibility/editable-anonymous-block.html
index 833a949f..e8db219 100644
--- a/third_party/blink/web_tests/accessibility/editable-anonymous-block.html
+++ b/third_party/blink/web_tests/accessibility/editable-anonymous-block.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="editable" contenteditable="true">
     Hello
@@ -9,7 +8,7 @@
 </div>
 
 <script>
-  test_after_layout_and_paint(() => {
+  test(() => {
     let editable = accessibilityController.accessibleElementById('editable');
     assert_not_equals(editable, null);
     assert_equals(editable.childrenCount, 2);
diff --git a/third_party/blink/web_tests/accessibility/editable-root.html b/third_party/blink/web_tests/accessibility/editable-root.html
index e7e14f0..cf66e44 100644
--- a/third_party/blink/web_tests/accessibility/editable-root.html
+++ b/third_party/blink/web_tests/accessibility/editable-root.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="main" role="main">
 
@@ -21,27 +20,27 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function() {
+test(function() {
     var axInput = accessibilityController.accessibleElementById('input');
     assert_true(axInput.isEditableRoot);
 }, 'An input element is an editable root.');
 
-test_after_layout_and_paint(function() {
+test(function() {
     var axContentEditable = accessibilityController.accessibleElementById('contenteditable-textbox');
     assert_true(axContentEditable.isEditableRoot);
 }, 'A content editable with a role of textbox is an editable root.');
 
-test_after_layout_and_paint(function() {
+test(function() {
     var axInnerInput = accessibilityController.accessibleElementById('inner-input');
     assert_true(axInnerInput.isEditableRoot);
 }, 'An input element embedded inside a content editable is an editable root.');
 
-test_after_layout_and_paint(function() {
+test(function() {
     var axInnerTextarea = accessibilityController.accessibleElementById('inner-textarea');
     assert_true(axInnerTextarea.isEditableRoot);
 }, 'A textarea embedded inside a content editable is an editable root.');
 
-test_after_layout_and_paint(function() {
+test(function() {
     var axContentEditable = accessibilityController.accessibleElementById('contenteditable-div');
     var axParagraph1 = accessibilityController.accessibleElementById('paragraph1');
     var axParagraph2 = accessibilityController.accessibleElementById('paragraph2');
diff --git a/third_party/blink/web_tests/accessibility/focus-action-clicks-element-in-active-descendant.html b/third_party/blink/web_tests/accessibility/focus-action-clicks-element-in-active-descendant.html
index 7e305626..f1b1894 100644
--- a/third_party/blink/web_tests/accessibility/focus-action-clicks-element-in-active-descendant.html
+++ b/third_party/blink/web_tests/accessibility/focus-action-clicks-element-in-active-descendant.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <table role="grid" aria-activedescendant="focused-cell" tabindex="-1" id="container-with-active-descendant">
   <tr role="row">
@@ -14,7 +13,7 @@
 <button id="focusable-element">Button</button>
 
 <script>
-test_after_layout_and_paint(() => {
+test(() => {
   if (document.activeElement)
     document.activeElement.blur();
 
@@ -33,7 +32,7 @@
   accessibilityFocusable.display = 'none';
 }, 'A click event should be dispatched if a non-focusable element can take accessibility focus via aria-activedescendant.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (document.activeElement)
     document.activeElement.blur();
 
@@ -51,7 +50,7 @@
   accessibilityFocusable.display = 'none';
 }, 'A click event should not be dispatched if a click handler is attached directly to an element that can take accessibility focus via aria-activedescendant.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (document.activeElement)
     document.activeElement.blur();
 
@@ -69,7 +68,7 @@
   nonFocusable.display = 'none';
 }, 'A click event should not be dispatched if an element cannot take accessibility focus.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (document.activeElement)
     document.activeElement.blur();
 
diff --git a/third_party/blink/web_tests/accessibility/focusable-div.html b/third_party/blink/web_tests/accessibility/focusable-div.html
index b13e428..f0cba76 100644
--- a/third_party/blink/web_tests/accessibility/focusable-div.html
+++ b/third_party/blink/web_tests/accessibility/focusable-div.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -20,7 +19,7 @@
 <div id="div7" tabindex="0">Initial text before list<ul><li>List item</li></ul></div>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var link = document.getElementById('link');
     link.focus();
     assert_equals(document.activeElement == link, true);
diff --git a/third_party/blink/web_tests/accessibility/focusable-span.html b/third_party/blink/web_tests/accessibility/focusable-span.html
index dd3ed9a..419e8ea 100644
--- a/third_party/blink/web_tests/accessibility/focusable-span.html
+++ b/third_party/blink/web_tests/accessibility/focusable-span.html
@@ -1,12 +1,11 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="p"><span tabindex="0">hello</span>there</p>
 
 <script>
-test_after_layout_and_paint(() => {
+test(() => {
   let axParagraph = accessibilityController.accessibleElementById('p');
   assert_not_equals(axParagraph, null);
   let axSpan = axParagraph.childAtIndex(0);
diff --git a/third_party/blink/web_tests/accessibility/font-changed.html b/third_party/blink/web_tests/accessibility/font-changed.html
index 2b9b31cf..2780e672 100644
--- a/third_party/blink/web_tests/accessibility/font-changed.html
+++ b/third_party/blink/web_tests/accessibility/font-changed.html
@@ -1,13 +1,12 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="fontFamily" style="font-family: serif">Font family.</p>
 <p id="fontSize" style="font-size: 200%">Font size.</p>
 
 <script>
-  test_after_layout_and_paint(() => {
+  test(() => {
     let fontFamily = document.getElementById('fontFamily');
     let axFontFamily = accessibilityController.accessibleElementById('fontFamily');
     assert_equals(axFontFamily.fontFamily, 'AXFontFamily: -webkit-serif');
@@ -15,7 +14,7 @@
     assert_equals(axFontFamily.fontFamily, 'AXFontFamily: -webkit-sans-serif');
   }, 'Ensures that the font family can be dynamically changed.');
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     let fontSize = document.getElementById('fontSize');
     let axFontSize = accessibilityController.accessibleElementById('fontSize');
     assert_equals(axFontSize.fontSize, 32);
diff --git a/third_party/blink/web_tests/accessibility/idref-newlines.html b/third_party/blink/web_tests/accessibility/idref-newlines.html
index 6527de9..6ee8731 100644
--- a/third_party/blink/web_tests/accessibility/idref-newlines.html
+++ b/third_party/blink/web_tests/accessibility/idref-newlines.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <input
@@ -14,7 +13,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axCheckbox = accessibilityController.accessibleElementById("chk1");
   assert_equals(axCheckbox.name, "nacho cheese");
 });
diff --git a/third_party/blink/web_tests/accessibility/image-inside-link.html b/third_party/blink/web_tests/accessibility/image-inside-link.html
index b778902..a400be6 100644
--- a/third_party/blink/web_tests/accessibility/image-inside-link.html
+++ b/third_party/blink/web_tests/accessibility/image-inside-link.html
@@ -1,14 +1,13 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="wrapper1">
     <a href="#"><img id="img1" alt="Delicious cake" src="resources/cake.png"></a>
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axImg = accessibilityController.accessibleElementById("img1");
     axImg.addNotificationListener(function(notification) {
@@ -30,7 +29,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var axEvent = false;
     var domEvent = false;
diff --git a/third_party/blink/web_tests/accessibility/image-map-bounds.html b/third_party/blink/web_tests/accessibility/image-map-bounds.html
index 7310bdd..11ea90e6 100644
--- a/third_party/blink/web_tests/accessibility/image-map-bounds.html
+++ b/third_party/blink/web_tests/accessibility/image-map-bounds.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <map name="imagemap1">
   <area shape="rect" coords="10,20,100,30" href="#" title="Link1" />
@@ -18,7 +17,7 @@
     findAllImageMapLinks(node.childAtIndex(i), list);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var links = [];
   findAllImageMapLinks(accessibilityController.rootElement, links);
 
diff --git a/third_party/blink/web_tests/accessibility/in-page-link-target.html b/third_party/blink/web_tests/accessibility/in-page-link-target.html
index 6ac4182..dadb2eed 100644
--- a/third_party/blink/web_tests/accessibility/in-page-link-target.html
+++ b/third_party/blink/web_tests/accessibility/in-page-link-target.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="main">
   <a id="anchor1" href="#emptyAnchor">Empty anchor</a>
@@ -20,7 +19,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor1');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
@@ -31,7 +30,7 @@
   assert_equals(target.name, '');
 }, 'Test finding the target when it is an empty anchor.');
 
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor2');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
@@ -42,7 +41,7 @@
   assert_equals(target.name, 'Anchor with content');
 }, 'Test finding the target when it is an anchor with content.');
 
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor3');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
@@ -53,7 +52,7 @@
   assert_equals(target.name, 'Anchor with ID');
 }, 'Test finding the target when it is an anchor with ID.');
 
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor4');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
@@ -62,7 +61,7 @@
   assert_equals(target.name, '');
 }, 'Test finding the target when it is an empty span.');
 
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor5');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
@@ -74,7 +73,7 @@
   assert_equals(child.name, 'Span with content');
 }, 'Test finding the target when it is a span with content.');
 
-test_after_layout_and_paint(function() {
+test(function() {
   var anchor = accessibilityController.accessibleElementById('anchor6');
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
diff --git a/third_party/blink/web_tests/accessibility/inline-text-bidi-bounds-for-range.html b/third_party/blink/web_tests/accessibility/inline-text-bidi-bounds-for-range.html
index 2b6ace4..c7e744e 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-bidi-bounds-for-range.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-bidi-bounds-for-range.html
@@ -4,7 +4,6 @@
 <meta charset="utf-8">
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -50,7 +49,7 @@
     assert_approx_equals(axBounds.height, rangeBounds.height, 2);
 }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     testWord('horizontalParagraph', 'one');
     testWord('horizontalParagraph', 'two');
     testWord('horizontalParagraph', 'three');
diff --git a/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range-br.html b/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range-br.html
index 41bc9f7..843b49e6 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range-br.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range-br.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="paragraph">Line 1<br>Line 2</p>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     // Due to rounding we won't get identical bounds as getBoundingClientRect(),
     // so allow the test to pass if we're within 1 pixel.
     var epsilon = 1;
diff --git a/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range.html b/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range.html
index e5b2e78..9fb7656 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-bounds-for-range.html
@@ -4,7 +4,6 @@
 <meta charset="utf-8">
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -20,7 +19,7 @@
     return Function('"use strict"; return (' + str  + ');')();
 }
 
-test_after_layout_and_paint((t) => {
+test((t) => {
     var axParagraph = accessibilityController.accessibleElementById('paragraph');
     var axStaticText = axParagraph.childAtIndex(0);
 
diff --git a/third_party/blink/web_tests/accessibility/inline-text-box-next-on-line.html b/third_party/blink/web_tests/accessibility/inline-text-box-next-on-line.html
index e5bb5b9..5b9f40b 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-box-next-on-line.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-box-next-on-line.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="paragraph">
   The <b id="quick">quick</b> brown fox
@@ -31,7 +30,7 @@
 </ol>
 
 <script>
-test_after_layout_and_paint(() => {
+test(() => {
   let axObj = accessibilityController.accessibleElementById('paragraph');
   // Find the first inline text box.
   while (axObj.childrenCount > 0)
@@ -59,7 +58,7 @@
   assert_array_equals(lineText, ['The ', 'quick', ' brown fox ']);
 }, 'Test |NextOnLine| and |PreviousOnLine| on |AXInlineTextBox|.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   let axEditable = accessibilityController.accessibleElementById('contentEditable');
   // There should be two lines in this content editable.
   assert_equals(axEditable.childrenCount, 2);
@@ -101,7 +100,7 @@
   }
 }, 'Test |NextOnLine| and |PreviousOnLine| on |AXLayoutObject|.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   let axObj = accessibilityController.accessibleElementById('paragraphWithLink');
   // There should be two static text children and a link in this paragraph.
   assert_equals(axObj.childrenCount, 3);
@@ -126,7 +125,7 @@
       'link', 'Paragraph with a ']);
 }, 'Test |NextOnLine| and |PreviousOnLine| on paragraphs with links.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   let axObj = accessibilityController.accessibleElementById('paragraphWithAnonymousBlock');
   // There should be three static text children in this paragraph.
   assert_equals(axObj.childrenCount, 3);
@@ -151,7 +150,7 @@
       'anonymous block', 'Paragraph with an ']);
 }, 'Test |NextOnLine| and |PreviousOnLine| on paragraphs with anonymous blocks.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   let axObj = accessibilityController.accessibleElementById('list');
   // There should be a list item in this list.
   assert_equals(axObj.childrenCount, 1);
diff --git a/third_party/blink/web_tests/accessibility/inline-text-change-style.html b/third_party/blink/web_tests/accessibility/inline-text-change-style.html
index e9b850ac..4464fd7 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-change-style.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-change-style.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -17,7 +16,7 @@
 
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     var axParagraph = accessibilityController.accessibleElementById('p');
     var axStaticText = axParagraph.childAtIndex(0);
     assert_equals(axStaticText.childrenCount, 2);
diff --git a/third_party/blink/web_tests/accessibility/inline-text-changes.html b/third_party/blink/web_tests/accessibility/inline-text-changes.html
index 7515f0b..e23c738 100644
--- a/third_party/blink/web_tests/accessibility/inline-text-changes.html
+++ b/third_party/blink/web_tests/accessibility/inline-text-changes.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -17,7 +16,7 @@
 
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     var notificationCalled = false;
 
     var axParagraph = accessibilityController.accessibleElementById('p');
diff --git a/third_party/blink/web_tests/accessibility/input-mixed.html b/third_party/blink/web_tests/accessibility/input-mixed.html
index 6f7c48fa..b62b689 100644
--- a/third_party/blink/web_tests/accessibility/input-mixed.html
+++ b/third_party/blink/web_tests/accessibility/input-mixed.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <!-- Will set indeterminate state via JS -->
 <input id="element1" type="checkbox" />
 <script>
@@ -42,130 +41,130 @@
     return accessibilityController.accessibleElementById(id);
   }
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element1");
     assert_equals(ax.checked, "mixed");
   }, "A native indeterminate checkbox must have the mixed state");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element2");
     assert_equals(ax.checked, "false");
   }, "A native checkbox that is not indeterminate" +
       " must NOT have the mixed state");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element3");
     assert_equals(ax.checked, "false");
   }, "A native radio that in a group with nothing checked" +
       " must appear unchecked, not mixed");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element4");
     assert_equals(ax.checked, "false");
   }, "A native radio that in a group with something checked" +
       " must NOT have the mixed state");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button");
     assert_equals(ax.checked, "");
   }, "<input type=button> does not set checked property");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-false");
     assert_equals(ax.checked, "false");
   }, "<input type=button aria-pressed=false> is not checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-false");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<input type=button aria-pressed=false> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-mixed");
     assert_equals(ax.checked, "mixed");
   }, "<input type=button aria-pressed=mixed> has checked state of mixed");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-mixed");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<input type=button aria-pressed=mixed> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-true");
     assert_equals(ax.checked, "true");
   }, "<input type=button aria-pressed=true> has checked state of true");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("input-button-true");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<input type=button aria-pressed=true> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button");
     assert_equals(ax.checked, "");
   }, "<div role=button> does not set checked property");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-false");
     assert_equals(ax.checked, "false");
   }, "<div role=button aria-pressed=false> is not checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-false");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<div role=button aria-pressed=false> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-mixed");
     assert_equals(ax.checked, "mixed");
   }, "<div role=button aria-pressed=mixed> has checked state of mixed");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-mixed");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<div role=button aria-pressed=mixed> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-true");
     assert_equals(ax.checked, "true");
   }, "<div role=button aria-pressed=true> has checked state of true");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("aria-button-true");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<div role=button aria-pressed=true> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button");
     assert_equals(ax.checked, "");
   }, "<button> does not set checked property");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-false");
     assert_equals(ax.checked, "false");
   }, "<button aria-pressed=false> is not checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-false");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<button aria-pressed=false> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-mixed");
     assert_equals(ax.checked, "mixed");
   }, "<button aria-pressed=mixed> has pressed state of checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-mixed");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<button aria-pressed=mixed> is a toggle button");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-true");
     assert_equals(ax.checked, "true");
   }, "<button aria-pressed=true> has checked state of true");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("button-true");
     assert_equals(ax.role, "AXRole: AXToggleButton");
   }, "<button aria-pressed=true> is a toggle button");
diff --git a/third_party/blink/web_tests/accessibility/input-type-range-value-change-event.html b/third_party/blink/web_tests/accessibility/input-type-range-value-change-event.html
index 6fe5c188..aa3e58d1 100644
--- a/third_party/blink/web_tests/accessibility/input-type-range-value-change-event.html
+++ b/third_party/blink/web_tests/accessibility/input-type-range-value-change-event.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <input id="range" type="range" min=0 max=10 value=5>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var target = document.getElementById("range");
     target.addEventListener("change", function() {
diff --git a/third_party/blink/web_tests/accessibility/input-type-text-value-change-event.html b/third_party/blink/web_tests/accessibility/input-type-text-value-change-event.html
index da62d21b..c9e176cf 100644
--- a/third_party/blink/web_tests/accessibility/input-type-text-value-change-event.html
+++ b/third_party/blink/web_tests/accessibility/input-type-text-value-change-event.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <input id="text" type="text" value="before">
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var target = document.getElementById("text");
     target.addEventListener("change", function() {
diff --git a/third_party/blink/web_tests/accessibility/is-ignored-change-sends-notification.html b/third_party/blink/web_tests/accessibility/is-ignored-change-sends-notification.html
index f215734..d9c6c71 100644
--- a/third_party/blink/web_tests/accessibility/is-ignored-change-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/is-ignored-change-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -40,7 +39,7 @@
 
 <div id="console"></div>
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     window.setTimeout(() => { assert_unreached("Did not receive all notifications within 1000ms"); }, 1000);
 
     function accessibleElementById(id) {
diff --git a/third_party/blink/web_tests/accessibility/language-attribute-and-meta-tag.html b/third_party/blink/web_tests/accessibility/language-attribute-and-meta-tag.html
index e81137b..3e3ba91 100644
--- a/third_party/blink/web_tests/accessibility/language-attribute-and-meta-tag.html
+++ b/third_party/blink/web_tests/accessibility/language-attribute-and-meta-tag.html
@@ -3,7 +3,6 @@
   <head>
     <script src="../resources/testharness.js"></script>
     <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
     <meta http-equiv="content-language" content="en, es">
   </head>
 
@@ -19,7 +18,7 @@
       window.axSpan = accessibilityController.accessibleElementById('span');
     });
 
-    test_after_layout_and_paint(() => {
+    test(() => {
       assert_equals(window.axBody.language, 'AXLanguage: en',
           'The first language in the meta tag should apply.');
       assert_equals(window.axParagraph.language, 'AXLanguage: pl',
@@ -28,7 +27,7 @@
           'The language in the lang attribute is inherited.');
     }, 'Tests if the content-language meta tag and the lang attribute are picked up by the accessibility component.');
 
-    test_after_layout_and_paint(() => {
+    test(() => {
       assert_equals(window.axBody.language, 'AXLanguage: en',
           'The lang attribute has not been changed yet.');
 
diff --git a/third_party/blink/web_tests/accessibility/language-in-canvas.html b/third_party/blink/web_tests/accessibility/language-in-canvas.html
index 528d80f..cd4d0a3 100644
--- a/third_party/blink/web_tests/accessibility/language-in-canvas.html
+++ b/third_party/blink/web_tests/accessibility/language-in-canvas.html
@@ -3,7 +3,6 @@
   <head>
     <script src="../resources/testharness.js"></script>
     <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
     <meta http-equiv="content-language" content="en, fr">
   </head>
 
@@ -21,7 +20,7 @@
       window.axSpan = accessibilityController.accessibleElementById('span');
     });
 
-    async_test_after_layout_and_paint((t) => {
+    async_test((t) => {
       internals.setUserPreferredLanguages([ 'de' ]);
 
       assert_equals(window.axCanvas.language, 'AXLanguage: en',
diff --git a/third_party/blink/web_tests/accessibility/language-meta-tag-dynamically-changing.html b/third_party/blink/web_tests/accessibility/language-meta-tag-dynamically-changing.html
index 031abd9..a9e6a41 100644
--- a/third_party/blink/web_tests/accessibility/language-meta-tag-dynamically-changing.html
+++ b/third_party/blink/web_tests/accessibility/language-meta-tag-dynamically-changing.html
@@ -3,7 +3,6 @@
   <head>
     <script src="../resources/testharness.js"></script>
     <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
     <!-- The second language in the meta tag should be ignored. -->
     <meta http-equiv="content-language" content="en, es">
   </head>
@@ -20,7 +19,7 @@
       window.axSpan = accessibilityController.accessibleElementById('span');
     });
 
-    async_test_after_layout_and_paint((t) => {
+    async_test((t) => {
       assert_equals(window.axBody.language, 'AXLanguage: en',
           'The meta tag has not been changed yet.');
 
diff --git a/third_party/blink/web_tests/accessibility/link-inside-button-accessible-text.html b/third_party/blink/web_tests/accessibility/link-inside-button-accessible-text.html
index b7b646d5..c8c525c 100644
--- a/third_party/blink/web_tests/accessibility/link-inside-button-accessible-text.html
+++ b/third_party/blink/web_tests/accessibility/link-inside-button-accessible-text.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 <div id="content">
@@ -16,7 +15,7 @@
 </div>
 <div id="console"></div>
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var button1 = accessibilityController.accessibleElementById("button1");
     assert_equals(button1.name, 'foo1');
     var button2 = accessibilityController.accessibleElementById("button2");
diff --git a/third_party/blink/web_tests/accessibility/list-with-selection.html b/third_party/blink/web_tests/accessibility/list-with-selection.html
index f7a6af32..9c90484 100644
--- a/third_party/blink/web_tests/accessibility/list-with-selection.html
+++ b/third_party/blink/web_tests/accessibility/list-with-selection.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <ul id="ul">
   <li>Item1</li>
@@ -24,7 +23,7 @@
 </ol>
 
 <script>
-  test_after_layout_and_paint(function() {
+  test(function() {
     if (!window.accessibilityController)
       return;
 
diff --git a/third_party/blink/web_tests/accessibility/listbox-focus.html b/third_party/blink/web_tests/accessibility/listbox-focus.html
index b90e205..017a2c8d 100644
--- a/third_party/blink/web_tests/accessibility/listbox-focus.html
+++ b/third_party/blink/web_tests/accessibility/listbox-focus.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <select id="listbox" size="3">
   <option id="item0">Alicia</option>
   <option id="item1">Peter</option>
@@ -9,7 +8,7 @@
   <option id="item3" disabled>Frank</option>
 </select>
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var listbox = document.getElementById("listbox");
     listbox.selectedIndex = 0;
@@ -44,12 +43,12 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axOption = axElementById("item2");
     assert_equals(axOption.isFocusable, true);
 }, "Listbox option is focusable");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axOption = axElementById("item3");
     assert_equals(axOption.isFocusable, false);
 }, "Disabled listbox option is not focusable");
diff --git a/third_party/blink/web_tests/accessibility/long-text.html b/third_party/blink/web_tests/accessibility/long-text.html
index 2cc88689..7b3930e 100644
--- a/third_party/blink/web_tests/accessibility/long-text.html
+++ b/third_party/blink/web_tests/accessibility/long-text.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <pre id="pre" style="word-wrap: break-word; white-space: pre-wrap;"><script>
 for (let count = 1; count <= 250; count ++)
   document.write('' + count + '. The quick brown fox jumps over the lazy dog. ');
@@ -9,7 +8,7 @@
 </body>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axPre = accessibilityController.accessibleElementById("pre");
     var axStaticText = axPre.childAtIndex(0);
     console.log(axStaticText.name);
diff --git a/third_party/blink/web_tests/accessibility/menu-list-open.html b/third_party/blink/web_tests/accessibility/menu-list-open.html
index 3e0c83b..aa5680a 100644
--- a/third_party/blink/web_tests/accessibility/menu-list-open.html
+++ b/third_party/blink/web_tests/accessibility/menu-list-open.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <select id="menulist">
   <option id="item0">Alicia</option>
   <option id="item1">Peter</option>
   <option id="item2">Kalinda</option>
 </select>
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var menulist = document.getElementById("menulist");
     menulist.selectedIndex = 0;
diff --git a/third_party/blink/web_tests/accessibility/menu-list-optgroup.html b/third_party/blink/web_tests/accessibility/menu-list-optgroup.html
index 9ff061db..01bcc43 100644
--- a/third_party/blink/web_tests/accessibility/menu-list-optgroup.html
+++ b/third_party/blink/web_tests/accessibility/menu-list-optgroup.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <select id="menulist">
   <option>Random Transformer</option>
   <optgroup>
@@ -16,7 +15,7 @@
   </optgroup>
 </select>
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     var axMenuList = accessibilityController.accessibleElementById("menulist");
     assert_equals(axMenuList.role, "AXRole: AXPopUpButton");
diff --git a/third_party/blink/web_tests/accessibility/menu-list-popup-reuses-objects.html b/third_party/blink/web_tests/accessibility/menu-list-popup-reuses-objects.html
index 43a3455..49b9759 100644
--- a/third_party/blink/web_tests/accessibility/menu-list-popup-reuses-objects.html
+++ b/third_party/blink/web_tests/accessibility/menu-list-popup-reuses-objects.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select id="menulist">
   <option id="item0">Alicia</option>
@@ -10,7 +9,7 @@
 </select>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     var menulist = document.getElementById("menulist");
     menulist.selectedIndex = 0;
 
diff --git a/third_party/blink/web_tests/accessibility/menu-list-selection-changed.html b/third_party/blink/web_tests/accessibility/menu-list-selection-changed.html
index 4d13bd3..9253d43 100644
--- a/third_party/blink/web_tests/accessibility/menu-list-selection-changed.html
+++ b/third_party/blink/web_tests/accessibility/menu-list-selection-changed.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select id="menulist">
   <option id="item0">Alicia</option>
@@ -10,7 +9,7 @@
 </select>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     var menulist = document.getElementById("menulist");
     menulist.selectedIndex = 0;
 
diff --git a/third_party/blink/web_tests/accessibility/menu-list-sends-change-notification.html b/third_party/blink/web_tests/accessibility/menu-list-sends-change-notification.html
index 57951ff..fd60464 100644
--- a/third_party/blink/web_tests/accessibility/menu-list-sends-change-notification.html
+++ b/third_party/blink/web_tests/accessibility/menu-list-sends-change-notification.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select id="menulist">
   <option selected>One</option>
@@ -10,7 +9,7 @@
 </select>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     var menulist = document.getElementById("menulist");
     menulist.focus();
     var accessibleMenulist = accessibilityController.focusedElement;
diff --git a/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html b/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
index df0967f..3b0ae97 100644
--- a/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
+++ b/third_party/blink/web_tests/accessibility/multiselect-list-reports-active-option.html
@@ -3,7 +3,20 @@
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
 <script src="../resources/run-after-layout-and-paint.js"></script>
+</head>
+<body>
+
+<select multiple id="menulist">
+  <option selected>One</option>
+  <option>Two</option>
+  <option>Three</option>
+  <option>Four</option>
+</select>
+
+<p id="description"></p>
+
 <script>
+// Wait until layout has settled to avoid notification spam.
 async_test_after_layout_and_paint((testCase) => {
     const expectedNotifications = [
         'ActiveDescendantChanged',
@@ -71,17 +84,5 @@
 }, 'Navigating in a multiselect list updates selection and the active selected option and sends a notification');
 
 </script>
-</head>
-<body>
-
-<select multiple id="menulist">
-  <option selected>One</option>
-  <option>Two</option>
-  <option>Three</option>
-  <option>Four</option>
-</select>
-
-<p id="description"></p>
-
 </body>
 </html>
diff --git a/third_party/blink/web_tests/accessibility/name-calc-aria-hidden.html b/third_party/blink/web_tests/accessibility/name-calc-aria-hidden.html
index 0fa5624..02be35c 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-aria-hidden.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-aria-hidden.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <h3 id="heading1">
@@ -13,7 +12,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axHeading1 = accessibilityController.accessibleElementById("heading1");
     assert_equals(axHeading1.name, "Before After");
     var axButton1 = accessibilityController.accessibleElementById("button1");
@@ -31,7 +30,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axHeading2 = accessibilityController.accessibleElementById("heading2");
     assert_equals(axHeading2.name, "Before After");
     var axButton2 = accessibilityController.accessibleElementById("button2");
@@ -49,7 +48,7 @@
 </div>
 
 <script>
- test_after_layout_and_paint(function(t) {
+ test(function(t) {
      var axHeading3 = accessibilityController.accessibleElementById("heading3");
      assert_equals(axHeading3, undefined);
      var axButton3 = accessibilityController.accessibleElementById("button3");
@@ -67,7 +66,7 @@
 </div>
 
 <script>
- test_after_layout_and_paint(function(t) {
+ test(function(t) {
      var axButton4 = accessibilityController.accessibleElementById("button4");
      assert_equals(axButton4.name, "Before After");
  }, "Aria-labelledby of aria-hidden subtree with another aria-hidden inside");
@@ -82,7 +81,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axInput5 = accessibilityController.accessibleElementById("input5");
     assert_equals(axInput5.name, "Before After");
 }, "Label can get accessible text from aria-hidden subtree");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-aria-label.html b/third_party/blink/web_tests/accessibility/name-calc-aria-label.html
index b645c5f..a2fe3b24 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-aria-label.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-aria-label.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <button id="labelOnly" aria-label="Label">Contents</button>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLabelOnly = accessibilityController.accessibleElementById("labelOnly");
     assert_equals(axLabelOnly.name, "Label");
     assert_equals(axLabelOnly.nameFrom, "attribute");
@@ -20,7 +19,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axEmptyLabel1 = accessibilityController.accessibleElementById("emptyLabel1");
     assert_equals(axEmptyLabel1.name, "Contents");
     assert_equals(axEmptyLabel1.nameFrom, "contents");
@@ -32,7 +31,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axEmptyLabel2 = accessibilityController.accessibleElementById("emptyLabel2");
     assert_equals(axEmptyLabel2.name, "Contents");
     assert_equals(axEmptyLabel2.nameFrom, "contents");
@@ -45,7 +44,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLabelledby1 = accessibilityController.accessibleElementById("labelledby1");
     assert_equals(axLabelledby1.name, "Labelledby 1");
     assert_equals(axLabelledby1.nameFrom, "relatedElement");
@@ -60,7 +59,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLabelledby2 = accessibilityController.accessibleElementById("labelledby2");
     assert_equals(axLabelledby2.name, "Label 2 label");
     assert_equals(axLabelledby2.nameFrom, "relatedElement");
@@ -75,7 +74,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLabelledby3 = accessibilityController.accessibleElementById("labelledby3");
     assert_equals(axLabelledby3.name, "Contents 3");
     assert_equals(axLabelledby3.nameFrom, "relatedElement");
@@ -91,7 +90,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLabelledby4 = accessibilityController.accessibleElementById("labelledby4");
     assert_equals(axLabelledby4.name, "Label 4 label");
     assert_equals(axLabelledby4.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-aria-labelledby.html b/third_party/blink/web_tests/accessibility/name-calc-aria-labelledby.html
index 3b91b6bc..21715c4 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-aria-labelledby.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-aria-labelledby.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <div id="div">Div Contents</div>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axDiv = accessibilityController.accessibleElementById("div");
     assert_equals(axDiv.name, "");
 }, "A simple div with inner text should not have an accessible name.");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axSelf = accessibilityController.accessibleElementById("self");
     assert_equals(axSelf.name, "Contents of button");
     assert_equals(axSelf.nameFrom, "contents");
@@ -32,7 +31,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby = accessibilityController.accessibleElementById("labelledby");
     assert_equals(axLabelledby.name, "Label 1");
     assert_equals(axLabelledby.nameFrom, "relatedElement");
@@ -46,7 +45,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby2 = accessibilityController.accessibleElementById("labelledby2");
     assert_equals(axLabelledby2.name, "Contents");
     assert_equals(axLabelledby2.nameFrom, "relatedElement");
@@ -61,7 +60,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby3 = accessibilityController.accessibleElementById("labelledby3");
     assert_equals(axLabelledby3.name, "Contents Label 3");
     assert_equals(axLabelledby3.nameFrom, "relatedElement");
@@ -78,7 +77,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby4 = accessibilityController.accessibleElementById("labelledby4");
     assert_equals(axLabelledby4.name, "Contents 4");
     assert_equals(axLabelledby4.nameFrom, "relatedElement");
@@ -92,7 +91,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby5 = accessibilityController.accessibleElementById("labelledby5");
     assert_equals(axLabelledby5.name, "Contents");
     assert_equals(axLabelledby5.nameFrom, "contents");
@@ -106,7 +105,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     var axLabelledby6 = accessibilityController.accessibleElementById("labelledby6");
     assert_equals(axLabelledby6.name, "");
     assert_equals(axLabelledby6.nameFrom, "relatedElement");
@@ -121,7 +120,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     assert_equals(accessibilityController.accessibleElementById("label7"), undefined);
     var axLabelledby7 = accessibilityController.accessibleElementById("labelledby7");
     assert_equals(axLabelledby7.name, "Invisible label");
@@ -137,7 +136,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t){
+test(function(t){
     assert_equals(accessibilityController.accessibleElementById("label8"), undefined);
     var axLabelledby8 = accessibilityController.accessibleElementById("labelledby8");
     assert_equals(axLabelledby8.name, "Display-none label");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-aria-owns.html b/third_party/blink/web_tests/accessibility/name-calc-aria-owns.html
index 0d5fba9..4ea2faf 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-aria-owns.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-aria-owns.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <input id="input1" aria-labelledby="list1">
@@ -13,7 +12,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axInput1 = accessibilityController.accessibleElementById("input1");
     assert_equals(axInput1.name, "A B C");
 }, "Aria-owns is considered in a name computation");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-button-inside-option.html b/third_party/blink/web_tests/accessibility/name-calc-button-inside-option.html
index ad7ec1c..f7ff439c 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-button-inside-option.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-button-inside-option.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <div role="listbox">
@@ -11,7 +10,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axOption1 = accessibilityController.accessibleElementById("option1");
     assert_equals(axOption1.name, "option 1");
     var axOption2 = accessibilityController.accessibleElementById("option2");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-figure.html b/third_party/blink/web_tests/accessibility/name-calc-figure.html
index faed9cbe..3f15cb2 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-figure.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-figure.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <figure id="figure1">
@@ -10,7 +9,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFigure1 = accessibilityController.accessibleElementById("figure1");
     assert_equals(axFigure1.name, "");
 }, "Figure element with no figcaption");
@@ -23,7 +22,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFigure2 = accessibilityController.accessibleElementById("figure2");
     assert_equals(axFigure2.name, "figure2-title");
     assert_equals(axFigure2.nameFrom, "title");
@@ -38,7 +37,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFigure3 = accessibilityController.accessibleElementById("figure3");
     assert_equals(axFigure3.name, "figcaption3");
     assert_equals(axFigure3.nameFrom, "relatedElement");
@@ -53,7 +52,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFigure4 = accessibilityController.accessibleElementById("figure4");
     assert_equals(axFigure4.name, "figure4-aria-label");
     assert_equals(axFigure4.nameFrom, "attribute");
@@ -69,7 +68,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axFigure5 = accessibilityController.accessibleElementById("figure5");
     assert_equals(axFigure5.name, "figure5-aria-labelledby");
     assert_equals(axFigure5.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-group-inside-treeitem.html b/third_party/blink/web_tests/accessibility/name-calc-group-inside-treeitem.html
index ec91fda..da92e5c359 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-group-inside-treeitem.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-group-inside-treeitem.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
   li { list-style-type: none; }
 </style>
@@ -29,12 +28,12 @@
 
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axParent1 = accessibilityController.accessibleElementById("parent1");
     assert_equals(axParent1.name.trim(), "Parent1");
 }, "Accessible name does not include display:block descendants with nameFrom:author");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axParent2 = accessibilityController.accessibleElementById("parent2");
     assert_equals(axParent2.name.trim(), "Parent2");
 }, "Accessible name does not include display:inline descendants with nameFrom:author");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-img.html b/third_party/blink/web_tests/accessibility/name-calc-img.html
index 50f87a09..5b5c921 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-img.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-img.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <img id="img1" src="resources/cake.png">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg1 = accessibilityController.accessibleElementById("img1");
     assert_equals(axImg1.name, "");
 }, "img element without alt");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg2 = accessibilityController.accessibleElementById("img2");
     assert_equals(axImg2.name, "img2-title");
     assert_equals(axImg2.nameFrom, "title");
@@ -31,7 +30,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg3 = accessibilityController.accessibleElementById("img3");
     assert_equals(axImg3.name, "img3-alt");
     assert_equals(axImg3.nameFrom, "attribute");
@@ -43,7 +42,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg4 = accessibilityController.accessibleElementById("img4");
     assert_equals(axImg4.name, "img4-aria-label");
     assert_equals(axImg4.nameFrom, "attribute");
@@ -56,7 +55,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg5 = accessibilityController.accessibleElementById("img5");
     assert_equals(axImg5.name, "img5-aria-labelledby");
     assert_equals(axImg5.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-inputs.html b/third_party/blink/web_tests/accessibility/name-calc-inputs.html
index 5a7d207..80b9ce3e 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-inputs.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-inputs.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <input id="text1" type="text">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput1 = accessibilityController.accessibleElementById("text1");
     assert_equals(axTextInput1.name, "");
 }, "Text input");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput2 = accessibilityController.accessibleElementById("text2");
     assert_equals(axTextInput2.name, "text2-title");
     assert_equals(axTextInput2.nameFrom, "title");
@@ -31,7 +30,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput3 = accessibilityController.accessibleElementById("text3");
     assert_equals(axTextInput3.name, "text3-placeholder");
     assert_equals(axTextInput3.nameFrom, "placeholder");
@@ -44,7 +43,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput4 = accessibilityController.accessibleElementById("text4");
     assert_equals(axTextInput4.name, "label-for-text4");
     assert_equals(axTextInput4.nameFrom, "relatedElement");
@@ -57,7 +56,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput5 = accessibilityController.accessibleElementById("text5");
     assert_equals(axTextInput5.name, "text5-aria-label");
     assert_equals(axTextInput5.nameFrom, "attribute");
@@ -71,7 +70,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput6 = accessibilityController.accessibleElementById("text6");
     assert_equals(axTextInput6.name, "labelledby-for-text6");
     assert_equals(axTextInput6.nameFrom, "relatedElement");
@@ -85,7 +84,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput7 = accessibilityController.accessibleElementById("text7");
     assert_equals(axTextInput7.name, "label-wrapping-text7");
     assert_equals(axTextInput7.nameFrom, "relatedElement");
@@ -99,7 +98,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput8 = accessibilityController.accessibleElementById("text8");
     assert_equals(axTextInput8.name, "");
 }, "Text input with wrapped label with wrong ID");
@@ -111,7 +110,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput9 = accessibilityController.accessibleElementById("text9");
     assert_equals(axTextInput9.name,
                   "label-for-text9 label-wrapping-text9");
@@ -125,7 +124,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput10 = accessibilityController.accessibleElementById("text10");
     assert_equals(axTextInput10.name, "label-wrapping-text10");
     assert_equals(axTextInput10.nameFrom, "relatedElement");
@@ -141,7 +140,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput11 = accessibilityController.accessibleElementById("text11");
     assert_equals(axTextInput11.name,
                   "first-label-for-text11 second-label-for-text11");
@@ -154,7 +153,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput12 = accessibilityController.accessibleElementById("text12");
     assert_equals(axTextInput12.name, "text12-aria-placeholder");
     assert_equals(axTextInput12.nameFrom, "placeholder");
@@ -166,7 +165,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput13 = accessibilityController.accessibleElementById("text13");
     assert_equals(axTextInput13.name, "text13-placeholder");
     assert_equals(axTextInput13.nameFrom, "placeholder");
@@ -178,7 +177,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput14 = accessibilityController.accessibleElementById("text14");
     assert_equals(axTextInput14.name, "text14-aria-placeholder");
     assert_equals(axTextInput14.nameFrom, "placeholder");
@@ -190,7 +189,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput15 = accessibilityController.accessibleElementById("text15");
     assert_equals(axTextInput15.name, "text15-aria-placeholder");
     assert_equals(axTextInput15.nameFrom, "placeholder");
@@ -201,7 +200,7 @@
     <div id="text16" role="button" aria-placeholder="text16-aria-placeholder">
 </div>
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axTextInput16 = accessibilityController.accessibleElementById("text16");
     assert_equals(axTextInput16.name, "");
     assert_equals(axTextInput16.nameFrom, "");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-native-markup-buttons.html b/third_party/blink/web_tests/accessibility/name-calc-native-markup-buttons.html
index e1e4f29..74a05f5 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-native-markup-buttons.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-native-markup-buttons.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <button id="button1"></button>
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton1 = accessibilityController.accessibleElementById("button1");
     assert_equals(axButton1.name, "");
 }, "Button with no content");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton2 = accessibilityController.accessibleElementById("button2");
     assert_equals(axButton2.name, "button2-content");
     assert_equals(axButton2.nameFrom, "contents");
@@ -31,7 +30,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton3 = accessibilityController.accessibleElementById("button3");
     assert_equals(axButton3.name, "");
 }, "Button with img content with no alt");
@@ -42,7 +41,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton4 = accessibilityController.accessibleElementById("button4");
     assert_equals(axButton4.name, "cake");
     assert_equals(axButton4.nameFrom, "contents");
@@ -54,7 +53,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton5 = accessibilityController.accessibleElementById("button5");
     assert_equals(axButton5.name, "I love !");
     assert_equals(axButton5.nameFrom, "contents");
@@ -66,7 +65,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton6 = accessibilityController.accessibleElementById("button6");
     assert_equals(axButton6.name, "I love cake !");
     assert_equals(axButton6.nameFrom, "contents");
@@ -78,7 +77,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton7 = accessibilityController.accessibleElementById("button7");
     assert_equals(axButton7.name, "button7-title");
     assert_equals(axButton7.nameFrom, "title");
@@ -90,7 +89,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton8 = accessibilityController.accessibleElementById("button8");
     assert_equals(axButton8.name, "button8-content");
     assert_equals(axButton8.nameFrom, "contents");
@@ -102,7 +101,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton9 = accessibilityController.accessibleElementById("button9");
     assert_equals(axButton9.name, "button9-title");
     assert_equals(axButton9.nameFrom, "title");
@@ -114,7 +113,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton10 = accessibilityController.accessibleElementById("button10");
     assert_equals(axButton10.name, "cake");
     assert_equals(axButton10.nameFrom, "contents");
@@ -127,7 +126,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButton11 = accessibilityController.accessibleElementById("button11");
     assert_equals(axButton11.name, "label-for-button11");
     assert_equals(axButton11.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-native-markup-input-buttons.html b/third_party/blink/web_tests/accessibility/name-calc-native-markup-input-buttons.html
index 3f58f11..851c38f4 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-native-markup-input-buttons.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-native-markup-input-buttons.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
     <input id="button1" type="button">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput1 = accessibilityController.accessibleElementById("button1");
     assert_equals(axButtonInput1.name, "");
 }, "Input button with no value");
@@ -19,7 +18,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput2 = accessibilityController.accessibleElementById("button2");
     assert_equals(axButtonInput2.name, "button-value2");
     assert_equals(axButtonInput2.nameFrom, "value");
@@ -31,7 +30,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput3 = accessibilityController.accessibleElementById("button3");
     assert_equals(axButtonInput3.name, "button-value3");
     assert_equals(axButtonInput3.nameFrom, "value");
@@ -43,7 +42,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput4 = accessibilityController.accessibleElementById("button4");
     assert_equals(axButtonInput4.name, "button-title4");
     assert_equals(axButtonInput4.nameFrom, "title");
@@ -56,7 +55,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput5 = accessibilityController.accessibleElementById("button5");
     assert_equals(axButtonInput5.name, "button-label-5");
     assert_equals(axButtonInput5.nameFrom, "relatedElement");
@@ -68,7 +67,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput6 = accessibilityController.accessibleElementById("button6");
     assert_equals(axButtonInput6.name, "button-label-6");
     assert_equals(axButtonInput6.nameFrom, "relatedElement");
@@ -81,7 +80,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput7 = accessibilityController.accessibleElementById("button7");
     assert_equals(axButtonInput7.name, "button-label-7");
     assert_equals(axButtonInput7.nameFrom, "relatedElement");
@@ -94,7 +93,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput8 = accessibilityController.accessibleElementById("button8");
     assert_equals(axButtonInput8.name, "button-aria-label-8");
     assert_equals(axButtonInput8.nameFrom, "attribute");
@@ -108,7 +107,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axButtonInput9 = accessibilityController.accessibleElementById("button9");
     assert_equals(axButtonInput9.name, "button9-aria-labelledby");
     assert_equals(axButtonInput9.nameFrom, "relatedElement");
@@ -120,7 +119,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton1 = accessibilityController.accessibleElementById("submit1");
     assert_equals(axSubmitButton1.name, "Submit");
     assert_equals(axSubmitButton1.nameFrom, "contents");
@@ -132,7 +131,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton2 = accessibilityController.accessibleElementById("submit2");
     assert_equals(axSubmitButton2.name, "submit-value2");
     assert_equals(axSubmitButton2.nameFrom, "value");
@@ -144,7 +143,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSubmitButton3 = accessibilityController.accessibleElementById("submit3");
     assert_equals(axSubmitButton3.name, "Submit");
     assert_equals(axSubmitButton3.nameFrom, "contents");
@@ -156,7 +155,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axResetButton1 = accessibilityController.accessibleElementById("reset1");
     assert_equals(axResetButton1.name, "Reset");
     assert_equals(axResetButton1.nameFrom, "contents");
@@ -168,7 +167,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImageInput1 = accessibilityController.accessibleElementById("image-input1");
     assert_equals(axImageInput1.name, "Submit");
     assert_equals(axImageInput1.nameFrom, "value");
@@ -180,7 +179,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImageInput2 = accessibilityController.accessibleElementById("image-input2");
     assert_equals(axImageInput2.name, "image-input-value2");
     assert_equals(axImageInput2.nameFrom, "attribute");
@@ -192,7 +191,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImageInput3 = accessibilityController.accessibleElementById("image-input3");
     assert_equals(axImageInput3.name, "image-input-alt3");
     assert_equals(axImageInput3.nameFrom, "attribute");
@@ -204,7 +203,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImageInput4 = accessibilityController.accessibleElementById("image-input4");
     assert_equals(axImageInput4.name, "image-input-alt4");
     assert_equals(axImageInput4.nameFrom, "attribute");
@@ -216,7 +215,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImageInput5 = accessibilityController.accessibleElementById("image-input5");
     assert_equals(axImageInput5.name, "image-input-title5");
     assert_equals(axImageInput5.nameFrom, "title");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-presentational.html b/third_party/blink/web_tests/accessibility/name-calc-presentational.html
index 9ecf355..7ce8ed6 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-presentational.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-presentational.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <div tabIndex=0 role="link" id="link1">
@@ -12,7 +11,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLink1 = accessibilityController.accessibleElementById("link1");
     assert_equals(axLink1.name, "I like ice cream.");
 }, "Presentational element ignored in name calculation..");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-summary.html b/third_party/blink/web_tests/accessibility/name-calc-summary.html
index d5085cb..9b0ace7 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-summary.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-summary.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <details id="details1">
@@ -10,7 +9,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axDetails1 = accessibilityController.accessibleElementById("details1");
     var axSummary1 = axDetails1.childAtIndex(0);
     assert_equals(axSummary1.role, "AXRole: AXDisclosureTriangle");
@@ -27,7 +26,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary2 = accessibilityController.accessibleElementById("summary2");
     assert_equals(axSummary2.name, "summary2-title");
     assert_equals(axSummary2.nameFrom, "title");
@@ -42,7 +41,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary3 = accessibilityController.accessibleElementById("summary3");
     assert_equals(axSummary3.name, "summary3-contents");
     assert_equals(axSummary3.nameFrom, "contents");
@@ -57,7 +56,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary4 = accessibilityController.accessibleElementById("summary4");
     assert_equals(axSummary4.name, "summary4-aria-label");
     assert_equals(axSummary4.nameFrom, "attribute");
@@ -73,7 +72,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axSummary5 = accessibilityController.accessibleElementById("summary5");
     assert_equals(axSummary5.name, "summary5-aria-labelledby");
     assert_equals(axSummary5.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-svg.html b/third_party/blink/web_tests/accessibility/name-calc-svg.html
index 550c741..d130a8a8 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-svg.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-svg.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container">
   <svg id="svg1">
@@ -10,7 +9,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axSvg1 = accessibilityController.accessibleElementById("svg1");
   assert_equals(axSvg1.name, "svg1-title");
   assert_equals(axSvg1.nameFrom, "relatedElement");
diff --git a/third_party/blink/web_tests/accessibility/name-calc-visibility.html b/third_party/blink/web_tests/accessibility/name-calc-visibility.html
index d0cd90c..a33115e9b 100644
--- a/third_party/blink/web_tests/accessibility/name-calc-visibility.html
+++ b/third_party/blink/web_tests/accessibility/name-calc-visibility.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="link1" class="container" tabIndex=0 role="link">
     <p>1</p>
@@ -16,7 +15,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axLink1 = accessibilityController.accessibleElementById("link1");
     assert_equals(axLink1.name, "1 2 7");
 }, "Visibility: 'hidden' and display: 'none' inside ARIA link");
@@ -39,7 +38,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axInput2 = accessibilityController.accessibleElementById("input2");
     assert_equals(axInput2.name, "1 2 7");
 }, "Visibility: 'hidden' and display: 'none' inside aria-labelledby label subtree");
@@ -60,7 +59,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axInput3 = accessibilityController.accessibleElementById("input3");
     assert_equals(axInput3.name, "1 2 3 4 6 7");
 }, "Visibility: 'hidden' and display: 'none' referenced directly by aria-labelledby");
@@ -85,7 +84,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axInput4 = accessibilityController.accessibleElementById("input4");
     assert_equals(axInput4.name, " 1 2 7 ");
 }, "Visibility: 'hidden' and display: 'none' inside aria-labelledby label subtree, where entire label subtree is display: 'none'");
diff --git a/third_party/blink/web_tests/accessibility/native-select-activedescendant.html b/third_party/blink/web_tests/accessibility/native-select-activedescendant.html
index d5ff686..b111e36 100644
--- a/third_party/blink/web_tests/accessibility/native-select-activedescendant.html
+++ b/third_party/blink/web_tests/accessibility/native-select-activedescendant.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select id="select" size="0">
 <option id="option1">Apple</option>
@@ -14,7 +13,7 @@
   return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(() => {
+test(() => {
   var select = document.querySelector('select');
   select.focus();
   select.selectedIndex = 0;
@@ -32,7 +31,7 @@
   assert_equals(axMenuListPopup.activeDescendant, axOption1);
 }, 'An active descendant should be exposed on the menu list popup when an option is selected and the select element is focused.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   var select = document.querySelector('select');
   select.blur();
   select.selectedIndex = 1;
diff --git a/third_party/blink/web_tests/accessibility/notification-listeners.html b/third_party/blink/web_tests/accessibility/notification-listeners.html
index 390f61a..af7e3ac 100644
--- a/third_party/blink/web_tests/accessibility/notification-listeners.html
+++ b/third_party/blink/web_tests/accessibility/notification-listeners.html
@@ -1,13 +1,12 @@
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select id="select" value="Select"></select>
 
 <div id="slider" tabindex="0" role="slider" aria-valuenow="5">Slider</div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     window.selectNotificationCount = 0;
     window.sliderNotificationCount = 0;
     window.globalNotificationCount = 0;
diff --git a/third_party/blink/web_tests/accessibility/option-aria-checked.html b/third_party/blink/web_tests/accessibility/option-aria-checked.html
index f29ea81..714aa6d 100644
--- a/third_party/blink/web_tests/accessibility/option-aria-checked.html
+++ b/third_party/blink/web_tests/accessibility/option-aria-checked.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <select>
 <option id="element1" role="menuitemcheckbox">1</option>
@@ -19,32 +18,32 @@
       return accessibilityController.accessibleElementById(id);
   }
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element1");
     assert_equals(ax.checked, "false");
   }, "<option> of role menuitemcheckbox is not checked by default");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element2");
     assert_equals(ax.checked, "true");
   }, "<option> of role menuitemcheckbox can be checked with aria-checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element3");
     assert_equals(ax.checked, "false");
   }, "<option> of role menuitemradio is not checked by default");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element4");
     assert_equals(ax.checked, "true");
   }, "<option> of role menuitemradio can be checked with aria-checked");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element5");
     assert_equals(ax.checked, "true");
   }, "<option> of no role is checked with aria-checked set");
 
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
     var ax = axElementById("element6");
     assert_equals(ax.checked, "");
   }, "Element of button role does not expose checked even with aria-checked set");
diff --git a/third_party/blink/web_tests/accessibility/option-removed-from-shadow-dom-crash.html b/third_party/blink/web_tests/accessibility/option-removed-from-shadow-dom-crash.html
index 94e5930..67e429d 100644
--- a/third_party/blink/web_tests/accessibility/option-removed-from-shadow-dom-crash.html
+++ b/third_party/blink/web_tests/accessibility/option-removed-from-shadow-dom-crash.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <script>
 /* doc > span */
@@ -38,7 +37,7 @@
  */
 textNode1.remove();
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   document.querySelector("option").remove();
 }, "Removing an option inside a shadow dom shouldn't crash");
 
diff --git a/third_party/blink/web_tests/accessibility/other-aria-attribute-change-sends-notification.html b/third_party/blink/web_tests/accessibility/other-aria-attribute-change-sends-notification.html
index da37b8c..9d8fd636 100644
--- a/third_party/blink/web_tests/accessibility/other-aria-attribute-change-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/other-aria-attribute-change-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -15,7 +14,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     function accessibleElementById(id) {
         return accessibilityController.accessibleElementById(id);
     }
diff --git a/third_party/blink/web_tests/accessibility/presentational-leaf.html b/third_party/blink/web_tests/accessibility/presentational-leaf.html
index a942489..ebf1cc7 100644
--- a/third_party/blink/web_tests/accessibility/presentational-leaf.html
+++ b/third_party/blink/web_tests/accessibility/presentational-leaf.html
@@ -1,14 +1,13 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div class="container" id="container1">
   <img id="img1" src="missing-image.png" alt="Missing">
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg1 = accessibilityController.accessibleElementById("img1");
     assert_equals(axImg1.name, "Missing");
     assert_equals(axImg1.childrenCount, 0);
@@ -20,7 +19,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axImg2 = accessibilityController.accessibleElementById("img2");
     assert_equals(axImg2, undefined);
     var axContainer2 = accessibilityController.accessibleElementById("container2");
diff --git a/third_party/blink/web_tests/accessibility/press-works-on-text-fields.html b/third_party/blink/web_tests/accessibility/press-works-on-text-fields.html
index 40a5e98..f29a6341 100644
--- a/third_party/blink/web_tests/accessibility/press-works-on-text-fields.html
+++ b/third_party/blink/web_tests/accessibility/press-works-on-text-fields.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <input id="input">
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   var input = document.getElementById("input");
   input.addEventListener("focus", function() {
     t.done();
@@ -19,7 +18,7 @@
 <textarea id="textarea"></textarea>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   var textarea = document.getElementById("textarea");
   textarea.addEventListener("focus", function() {
     t.done();
@@ -32,7 +31,7 @@
 <div id="contenteditable" contenteditable></div>
 
 <script>
-async_test_after_layout_and_paint(function(t) {
+async_test(function(t) {
   var contenteditable = document.getElementById("contenteditable");
   contenteditable.addEventListener("focus", function() {
     t.done();
diff --git a/third_party/blink/web_tests/accessibility/reach-and-scroll-overflow-div-without-mouse.html b/third_party/blink/web_tests/accessibility/reach-and-scroll-overflow-div-without-mouse.html
index 454a246..e5208031 100644
--- a/third_party/blink/web_tests/accessibility/reach-and-scroll-overflow-div-without-mouse.html
+++ b/third_party/blink/web_tests/accessibility/reach-and-scroll-overflow-div-without-mouse.html
@@ -28,16 +28,16 @@
 <script src="../resources/run-after-layout-and-paint.js"></script>
 <script src="../fast/spatial-navigation/resources/snav-testharness.js"></script>
 <script>
-  test_after_layout_and_paint(function(t) {
+  test(function(t) {
       assert_true(internals.runtimeFlags.keyboardFocusableScrollersEnabled);
   }, "Make sure KeyboardFocusableScrollers is set.");
 
   let scroller = document.getElementById('scroller')
-  test_after_layout_and_paint(() => {
+  test(() => {
     assert_equals(scroller.scrollTop, 0);
   }, "Scroller starts at its top.");
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     assert_equals(scroller.tabIndex, 0);
   }, "tabIndex reflects reality (the scroller can be focused).");
 
diff --git a/third_party/blink/web_tests/accessibility/role-change.html b/third_party/blink/web_tests/accessibility/role-change.html
index b92b5a4d..ab29560 100644
--- a/third_party/blink/web_tests/accessibility/role-change.html
+++ b/third_party/blink/web_tests/accessibility/role-change.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="grid">
   <div role="presentation" id="row1">
@@ -32,7 +31,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     const axPresentation = axElementById('row1');
     assert_equals(axPresentation, undefined);
 
@@ -43,7 +42,7 @@
     assert_equals(axRow.role, 'AXRole: AXRow');
 }, "Role presentation changes to role row.");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_equals(axElementById('table'), undefined);
     assert_equals(axElementById('tr1'), undefined);
     assert_equals(axElementById('td1').role, "AXRole: AXGenericContainer");
@@ -68,7 +67,7 @@
     assert_equals(axElementById('td1').role, "AXRole: AXGenericContainer");
 }, "Role presentation toggled on table.");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     assert_equals(axElementById('opt1').role, "AXRole: AXListBoxOption");
     assert_equals(axElementById('select1').role, "AXRole: AXListBox");
 
@@ -83,7 +82,7 @@
 
 }, "Select size change.");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     const axInput = axElementById('input');
     assert_equals(axInput.role, "AXRole: AXRadioButton");
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-containers.html b/third_party/blink/web_tests/accessibility/scroll-containers.html
index 5a49dd2..40c3066a 100644
--- a/third_party/blink/web_tests/accessibility/scroll-containers.html
+++ b/third_party/blink/web_tests/accessibility/scroll-containers.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container">
   <div style="border: 1px solid #000; height: 5000px;">5000-pixel box</div>
@@ -18,7 +17,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var axWebArea = accessibilityController.rootElement;
     assert_equals(axWebArea.role, "AXRole: AXWebArea");
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
index b96bbf4..05afb57 100644
--- a/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-div-horiz-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
   <script src="../resources/testharness.js"></script>
   <script src="../resources/testharnessreport.js"></script>
-  <script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
 .container {
   padding: 100px;
@@ -34,7 +33,7 @@
 
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
   var container = document.getElementById('container');
 
   accessibilityController.addNotificationListener(t.step_func((target, notification) => {
diff --git a/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
index 987ba4a4..af38aa7 100644
--- a/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-div-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
 .container {
   padding: 100px;
@@ -29,7 +28,7 @@
 
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
   var container = document.getElementById('container');
 
   accessibilityController.addNotificationListener(t.step_func((target, notification) => {
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
index 625aab5..61ef021 100644
--- a/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-window-horiz-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
 .bigbutton {
     display:block;
@@ -20,7 +19,7 @@
 
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
   window.scrollTo(0, 0);
   assert_equals(window.pageXOffset, 0);
 
diff --git a/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
index 084f0fa..b5387a9 100644
--- a/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
+++ b/third_party/blink/web_tests/accessibility/scroll-window-sends-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 <style>
 .bigbutton {
     display:block;
@@ -21,7 +20,7 @@
 <div id="console"></div>
 <script>
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
   window.scrollTo(0, 0);
   assert_equals(window.pageXOffset, 0);
 
diff --git a/third_party/blink/web_tests/accessibility/selection-affinity.html b/third_party/blink/web_tests/accessibility/selection-affinity.html
index 737fb0e..60d31d8 100644
--- a/third_party/blink/web_tests/accessibility/selection-affinity.html
+++ b/third_party/blink/web_tests/accessibility/selection-affinity.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="main" role="main">
 
@@ -13,7 +12,7 @@
 <div id="console"></div>
 
 <script>
-    test_after_layout_and_paint(function()
+    test(function()
     {
         // Create a range selecting more and more characters of text until
         // the bounding rect has a nonzero width, indicating we got the first
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html b/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
index 89f26ded..b4a8eab5 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-aria-textbox.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="static_eventTarget">
 
@@ -15,7 +14,7 @@
 
 <div id="console"></div>
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     // This forces building the accessibility tree, because selection change
     // events only fire on elements that already exist.
     accessibilityController.accessibleElementById('dummy');
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html b/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
index 831ad710..0422c4c 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-contenteditable.html
@@ -14,6 +14,7 @@
 </div>
 
 <script>
+// Wait until layout has settled to avoid notification spam.
 async_test_after_layout_and_paint((t) => {
     var element = document.getElementById('contentEditable');
     element.focus();
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-input.html b/third_party/blink/web_tests/accessibility/selection-change-notification-input.html
index 6fd4b11..6a7066a 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-input.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-input.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="static_eventTarget">
 
@@ -13,7 +12,7 @@
 <script>
 window.jsTestIsAsync = true;
 
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     // This forces building the accessibility tree, because selection change
     // events only fire on elements that already exist.
     accessibilityController.accessibleElementById('dummy');
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-statictext.html b/third_party/blink/web_tests/accessibility/selection-change-notification-statictext.html
index 10617ee..0e9a9a4 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-statictext.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-statictext.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="static_eventTarget">
 
@@ -11,7 +10,7 @@
 
 <div id="console"></div>
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     var axElement = accessibilityController.rootElement;
     axElement.addNotificationListener(t.step_func(function(notification) {
         if (notification == 'SelectedTextChanged') {
diff --git a/third_party/blink/web_tests/accessibility/selection-change-notification-textarea.html b/third_party/blink/web_tests/accessibility/selection-change-notification-textarea.html
index 727170c0..1596574 100644
--- a/third_party/blink/web_tests/accessibility/selection-change-notification-textarea.html
+++ b/third_party/blink/web_tests/accessibility/selection-change-notification-textarea.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="static_eventTarget">
 
@@ -11,7 +10,7 @@
 
 <div id="console"></div>
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     // This forces building the accessibility tree, because selection change
     // events only fire on elements that already exist.
     accessibilityController.accessibleElementById('dummy');
diff --git a/third_party/blink/web_tests/accessibility/selection-events.html b/third_party/blink/web_tests/accessibility/selection-events.html
index c05d84d..74c92e7 100644
--- a/third_party/blink/web_tests/accessibility/selection-events.html
+++ b/third_party/blink/web_tests/accessibility/selection-events.html
@@ -1,12 +1,11 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="para">This is a test</p>
 
 <script>
-    test_after_layout_and_paint(() =>
+    test(() =>
     {
         var para = document.getElementById('para');
         var selectstartCount = 0;
diff --git a/third_party/blink/web_tests/accessibility/selection-follows-focus.html b/third_party/blink/web_tests/accessibility/selection-follows-focus.html
index 1b3ab3b..9872be0 100644
--- a/third_party/blink/web_tests/accessibility/selection-follows-focus.html
+++ b/third_party/blink/web_tests/accessibility/selection-follows-focus.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="listbox" id="listbox" tabindex="0" aria-activedescendant="opt3">
     <div id="opt1" role="option">Option 1</div>
@@ -25,13 +24,13 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     listbox.focus();
     var axOption1 = axElementById("opt1");
     assert_equals(axOption1.isSelectable, true);
 }, "Descendant widgets are selectable in a single selection container");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     listbox.focus();
     listbox.setAttribute("aria-activedescendant", "opt1");
     var axOption1 = axElementById("opt1");
@@ -39,32 +38,32 @@
 }, "Selection follows activedescendant in a single selection container");
 
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axOption = axElementById("opt2.1");
   assert_equals(axOption.isSelectable, true);
 }, "Options are selectable even if it is not clear they can be from markup");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     listbox.focus();
     listbox.setAttribute("aria-activedescendant", "opt2");
     var axOption2 = axElementById("opt2");
     assert_equals(axOption2.isSelected, false);
 }, "Selection doesn't follow activedescendant when aria-selected=false");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     listbox.focus();
     listbox.setAttribute("aria-activedescendant", "opt2");
     var axOption1 = axElementById("opt1");
     assert_equals(axOption1.isSelected, false);
 }, "Only focused item is marked as selected in a single selection container");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     document.getElementById('treeitem1').focus();
     var treeitem1 = axElementById("treeitem1");
     assert_equals(treeitem1.isSelected, true);
 }, "Selection follows tabindex focus in a single selection container");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     document.getElementById('treeitem2').focus();
     var treeitem2 = axElementById("treeitem2");
     assert_equals(treeitem2.isSelected, false);
diff --git a/third_party/blink/web_tests/accessibility/set-selection-child-offset.html b/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
index 127aa76..e3ae16b 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-child-offset.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div role="region" id="region">
   <span role="none" id="span1">This is a<a id="link" href="#1">test</a></span>
@@ -29,7 +28,7 @@
     window.lineBreak = span1.nextSibling;
   });
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     axRegion.setSelection(axRegion, 0, axRegion, 0);
     verifySelection(text1, 0, text1, 0, '');
   }, 'Test creating a collapsed selection before the first character of the first span.');
diff --git a/third_party/blink/web_tests/accessibility/set-selection-link.html b/third_party/blink/web_tests/accessibility/set-selection-link.html
index a528e38..b66021e 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-link.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-link.html
@@ -1,12 +1,11 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <p id="para">This<br> is a<a href="#g">test</a>of selection</p>
 
 <script>
-  test_after_layout_and_paint(() => {
+  test(() => {
     const lastText = document.getElementById('para').lastChild;
     assert_class_string(lastText, 'Text');
 
diff --git a/third_party/blink/web_tests/accessibility/set-selection-whitespace.html b/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
index 9706d0f5..ec598fb4 100644
--- a/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
+++ b/third_party/blink/web_tests/accessibility/set-selection-whitespace.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="editable1" contenteditable="true">Hello1</div>
 
@@ -10,7 +9,7 @@
 <h2 id="heading">  Hello2  </h2>
 
 <script>
-  test_after_layout_and_paint(() => {
+  test(() => {
     const editable1 = document.getElementById('editable1');
     assert_class_string(editable1, 'HTMLDivElement');
     const textNode1 = editable1.firstChild;
@@ -35,7 +34,7 @@
     assert_equals(selection.toString(), 'Hello1', 'getSelection.toString()');
   }, 'Using accessible APIs to set selection works when there is no extra whitespace.');
 
-test_after_layout_and_paint(() => {
+test(() => {
     const editable2 = document.getElementById('editable2');
     assert_class_string(editable2, 'HTMLDivElement');
     const textNode2 = editable2.firstChild;
@@ -58,7 +57,7 @@
     assert_equals(selection.toString(), 'Hello2', 'getSelection.toString()');
   }, 'Using accessible APIs to set selection works even with non-visible whitespace.');
 
-  test_after_layout_and_paint(() => {
+  test(() => {
     const heading = document.getElementById('heading');
     assert_class_string(heading, 'HTMLHeadingElement');
     const headingText = heading.firstChild;
diff --git a/third_party/blink/web_tests/accessibility/show-context-menu-crash.html b/third_party/blink/web_tests/accessibility/show-context-menu-crash.html
index 9d12ca1..8f06ccd 100644
--- a/third_party/blink/web_tests/accessibility/show-context-menu-crash.html
+++ b/third_party/blink/web_tests/accessibility/show-context-menu-crash.html
@@ -1,10 +1,9 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <script>
-test_after_layout_and_paint(function(t)
+test(function(t)
 {
     accessibilityController.focusedElement.showMenu();
 }, "showing the context menu on the document element should not crash");
diff --git a/third_party/blink/web_tests/accessibility/show-context-menu-shadowdom.html b/third_party/blink/web_tests/accessibility/show-context-menu-shadowdom.html
index d092626..a907edb 100644
--- a/third_party/blink/web_tests/accessibility/show-context-menu-shadowdom.html
+++ b/third_party/blink/web_tests/accessibility/show-context-menu-shadowdom.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <style>
 #outer {
@@ -47,7 +46,7 @@
 var shadowRoot = shadowHost.createShadowRoot();
 shadowRoot.appendChild(document.getElementById('inner'));
 
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     target.addEventListener('contextmenu', function() {
         document.getElementById("outer").style.display = "none";
diff --git a/third_party/blink/web_tests/accessibility/show-context-menu.html b/third_party/blink/web_tests/accessibility/show-context-menu.html
index 8ca992b..9a8f2d3 100644
--- a/third_party/blink/web_tests/accessibility/show-context-menu.html
+++ b/third_party/blink/web_tests/accessibility/show-context-menu.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <style>
 #outer {
@@ -30,7 +29,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint(function(t)
+async_test(function(t)
 {
     var target = document.getElementById("inner");
     target.addEventListener('contextmenu', function() {
diff --git a/third_party/blink/web_tests/accessibility/slider-thumb-bounds.html b/third_party/blink/web_tests/accessibility/slider-thumb-bounds.html
index 123e14d3..262520e 100644
--- a/third_party/blink/web_tests/accessibility/slider-thumb-bounds.html
+++ b/third_party/blink/web_tests/accessibility/slider-thumb-bounds.html
@@ -1,12 +1,11 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <input id="slider" type="range" min=0 max=10 value=0 style="position: absolute; left: 100px; top: 50px; width: 300px; height: 20px;">
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axSlider = accessibilityController.accessibleElementById("slider");
   assert_equals(axSlider.role, "AXRole: AXSlider");
 
diff --git a/third_party/blink/web_tests/accessibility/spelling-markers.html b/third_party/blink/web_tests/accessibility/spelling-markers.html
index d4fa57f..d5a7c4b 100644
--- a/third_party/blink/web_tests/accessibility/spelling-markers.html
+++ b/third_party/blink/web_tests/accessibility/spelling-markers.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="editable" contentEditable="true" spellcheck="true">Test spelling markers</div>
 
@@ -12,7 +11,7 @@
 <textarea id="textarea" spellcheck="true">Test spelling markers</textarea>
 
 <script>
-test_after_layout_and_paint(() => {
+test(() => {
   if (!window.internals)
     return;
 
@@ -35,7 +34,7 @@
   assert_equals(axStaticText.misspellingAtIndex(1), 'markers');
 }, 'Spelling markers should be reported in content editables.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (!window.internals)
     return;
 
@@ -61,7 +60,7 @@
   document.designMode = 'off';
 }, 'Spelling markers should be reported in static text when design mode is on.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (!window.internals)
     return;
 
@@ -86,7 +85,7 @@
   assert_equals(axStaticText.misspellingAtIndex(0), 'spelling');
 }, 'Spelling markers should be reported in input text fields.');
 
-test_after_layout_and_paint(() => {
+test(() => {
   if (!window.internals)
     return;
 
diff --git a/third_party/blink/web_tests/accessibility/table-caption.html b/third_party/blink/web_tests/accessibility/table-caption.html
index 1e7d2ed..08cdbaf 100644
--- a/third_party/blink/web_tests/accessibility/table-caption.html
+++ b/third_party/blink/web_tests/accessibility/table-caption.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body id="body">
 
@@ -18,7 +17,7 @@
 
 <script>
 
-test_after_layout_and_paint((t) => {
+test((t) => {
   var table = accessibilityController.accessibleElementById("table");
     assert_equals(table.childrenCount, 2);
     window.captionText = table.childAtIndex(0).childAtIndex(0).name;
diff --git a/third_party/blink/web_tests/accessibility/table-dynamic-properties.html b/third_party/blink/web_tests/accessibility/table-dynamic-properties.html
index f3d1015..ae39464 100644
--- a/third_party/blink/web_tests/accessibility/table-dynamic-properties.html
+++ b/third_party/blink/web_tests/accessibility/table-dynamic-properties.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <table id="table1">
 </table>
@@ -11,7 +10,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     // Check beginning row and column count.
     var axTable = axElementById("table1");
     assert_equals(axTable.rowCount, 0);
diff --git a/third_party/blink/web_tests/accessibility/table-with-grid-roles.html b/third_party/blink/web_tests/accessibility/table-with-grid-roles.html
index 36eef25..935a30a 100644
--- a/third_party/blink/web_tests/accessibility/table-with-grid-roles.html
+++ b/third_party/blink/web_tests/accessibility/table-with-grid-roles.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container">
   <table id="table1" role="grid">
@@ -35,7 +34,7 @@
 <div id="console"></div>
 
 <script>
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axTable = accessibilityController.accessibleElementById('table1');
   assert_equals(axTable.role, "AXRole: AXGrid");
 
@@ -43,7 +42,7 @@
   assert_equals(axTable.columnCount, 2);
 }, "Test table with ARIA grid role on just table element.");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axTable = accessibilityController.accessibleElementById('table2');
   assert_equals(axTable.role, "AXRole: AXGrid");
 
@@ -51,7 +50,7 @@
   assert_equals(axTable.columnCount, 2);
 }, "Test table with ARIA grid role on table and row role on rows.");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
   var axTable = accessibilityController.accessibleElementById('table3');
   assert_equals(axTable.role, "AXRole: AXGrid");
 
diff --git a/third_party/blink/web_tests/accessibility/table-with-presentation-role.html b/third_party/blink/web_tests/accessibility/table-with-presentation-role.html
index 6ab3b15..16bc714 100644
--- a/third_party/blink/web_tests/accessibility/table-with-presentation-role.html
+++ b/third_party/blink/web_tests/accessibility/table-with-presentation-role.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container">
   <table role="presentation">
@@ -15,7 +14,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint(() => {
+test(() => {
   var axTableContainer = accessibilityController.accessibleElementById('container');
   assert_equals(axTableContainer.childrenCount, 4);
   var axTextContainer1 = axTableContainer.childAtIndex(0);
diff --git a/third_party/blink/web_tests/accessibility/table-with-th-role-gridcell-crash.html b/third_party/blink/web_tests/accessibility/table-with-th-role-gridcell-crash.html
index 547f59a7..b2295324 100644
--- a/third_party/blink/web_tests/accessibility/table-with-th-role-gridcell-crash.html
+++ b/third_party/blink/web_tests/accessibility/table-with-th-role-gridcell-crash.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <style>
 tr {
@@ -17,7 +16,7 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("cell1");
     assert_equals(ax.role, "AXRole: AXCell");
 }, "A <th role='gridcell'> in a table with extra layout objects doesn't crash");
diff --git a/third_party/blink/web_tests/accessibility/text-change-notification.html b/third_party/blink/web_tests/accessibility/text-change-notification.html
index 9e6640a..947ffe5 100644
--- a/third_party/blink/web_tests/accessibility/text-change-notification.html
+++ b/third_party/blink/web_tests/accessibility/text-change-notification.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -18,7 +17,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     function accessibleElementById(id) {
         return accessibilityController.accessibleElementById(id);
     }
diff --git a/third_party/blink/web_tests/accessibility/text-changes-with-relations.html b/third_party/blink/web_tests/accessibility/text-changes-with-relations.html
index 34630f5f..6af930d 100644
--- a/third_party/blink/web_tests/accessibility/text-changes-with-relations.html
+++ b/third_party/blink/web_tests/accessibility/text-changes-with-relations.html
@@ -1,7 +1,6 @@
 <!DOCTYPE HTML>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <!DOCTYPE html>
 <html>
@@ -27,21 +26,21 @@
     return accessibilityController.accessibleElementById(id);
 }
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("input");
     assert_equals(ax.name, "cats");
     document.getElementById("input-name").innerText = "dogs";
     assert_equals(ax.name, "dogs");
 }, "Changing text in a <label for> changes a name pointed to by @for");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("main");
     assert_equals(ax.name, "apples");
     document.getElementById("main-name").innerText = "oranges";
     assert_equals(ax.name, "oranges");
 }, "Changing text pointed to by aria-labelledby changes the name pointed to by @aria-labelledby");
 
-test_after_layout_and_paint(function(t) {
+test(function(t) {
     var ax = axElementById("nav");
     assert_equals(ax.description, "butter");
     document.getElementById("nav-desc").innerText = "margarine";
diff --git a/third_party/blink/web_tests/accessibility/title-ui-element-correctness.html b/third_party/blink/web_tests/accessibility/title-ui-element-correctness.html
index 0309d5d..5384354c 100644
--- a/third_party/blink/web_tests/accessibility/title-ui-element-correctness.html
+++ b/third_party/blink/web_tests/accessibility/title-ui-element-correctness.html
@@ -3,7 +3,6 @@
 <body>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 
 <div id="container">
   <div>
@@ -42,7 +41,7 @@
 </div>
 
 <script>
-async_test_after_layout_and_paint((t) => {
+async_test((t) => {
     function hasTitleUIElement(axElement) {
         var label1 = accessibilityController.accessibleElementById("label1");
         var titleUIElement = axElement.nameElementAtIndex(0);
diff --git a/third_party/blink/web_tests/accessibility/whitespace-in-name-calc.html b/third_party/blink/web_tests/accessibility/whitespace-in-name-calc.html
index 15db45f..ed97a8a 100644
--- a/third_party/blink/web_tests/accessibility/whitespace-in-name-calc.html
+++ b/third_party/blink/web_tests/accessibility/whitespace-in-name-calc.html
@@ -3,7 +3,6 @@
 <head>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
-<script src="../resources/run-after-layout-and-paint.js"></script>
 </head>
 <body>
 
@@ -38,7 +37,7 @@
 </div>
 
 <script>
-test_after_layout_and_paint((t) => {
+test((t) => {
     function accessibleTitle(id) {
       var axObject = accessibilityController.accessibleElementById(id);
       return axObject.name.replace('', '');
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration-ref.html
deleted file mode 100644
index 81b01bd..0000000
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration-ref.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<title>Reference for WorkletAnimation should continue to be in effect forever, even if its duration is passed</title>
-<style>
-#box {
-  width: 100px;
-  height: 100px;
-  background-color: green;
-  transform: translateY(100px);
-}
-</style>
-
-<div id="box"></div>
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration.https.html
index 1526004e..1a8afc1 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-duration.https.html
@@ -1,7 +1,6 @@
 <html>
 <title>WorkletAnimation should continue to be in effect forever, even if its duration is passed</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
-<link rel="match" href="worklet-animation-duration-ref.html">
 
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
index b18097b1..936b39e 100644
--- a/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
+++ b/third_party/blink/web_tests/external/wpt/common/security-features/resources/common.js
@@ -745,6 +745,136 @@
     });
 }
 
+// Subresource paths and invokers.
+const subresourceMap = {
+  "a-tag": {
+    path: "/common/security-features/subresource/document.py",
+    invoker: requestViaAnchor,
+  },
+  "area-tag": {
+    path: "/common/security-features/subresource/document.py",
+    invoker: requestViaArea,
+  },
+  "audio-tag": {
+    path: "/common/security-features/subresource/audio.py",
+    invoker: requestViaAudio,
+  },
+  "beacon-request": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: requestViaSendBeacon,
+  },
+  "fetch-request": {
+    path: "/common/security-features/subresource/xhr.py",
+    invoker: requestViaFetch,
+  },
+  "form-tag": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: requestViaForm,
+  },
+  "iframe-tag": {
+    path: "/common/security-features/subresource/document.py",
+    invoker: requestViaIframe,
+  },
+  "img-tag": {
+    path: "/common/security-features/subresource/image.py",
+    invoker: requestViaImage,
+    invokerForReferrerPolicy: requestViaImageForReferrerPolicy,
+  },
+  "link-css-tag": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: requestViaLinkStylesheet,
+  },
+  "link-prefetch-tag": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: requestViaLinkPrefetch,
+  },
+  "object-tag": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: requestViaObject,
+  },
+  "picture-tag": {
+    path: "/common/security-features/subresource/image.py",
+    invoker: requestViaPicture,
+  },
+  "script-tag": {
+    path: "/common/security-features/subresource/script.py",
+    invoker: requestViaScript,
+  },
+  "video-tag": {
+    path: "/common/security-features/subresource/video.py",
+    invoker: requestViaVideo,
+  },
+  "xhr-request": {
+    path: "/common/security-features/subresource/xhr.py",
+    invoker: requestViaXhr,
+  },
+
+  "worker-request": {
+    path: "/common/security-features/subresource/worker.py",
+    invoker: url => requestViaDedicatedWorker(url),
+  },
+  // TODO: Merge "module-worker" and "module-worker-top-level".
+  "module-worker": {
+    path: "/common/security-features/subresource/worker.py",
+    invoker: url => requestViaDedicatedWorker(url, {type: "module"}),
+  },
+  "module-worker-top-level": {
+    path: "/common/security-features/subresource/worker.py",
+    invoker: url => requestViaDedicatedWorker(url, {type: "module"}),
+  },
+  "module-data-worker-import": {
+    path: "/common/security-features/subresource/worker.py",
+    invoker: url =>
+        requestViaDedicatedWorker(workerUrlThatImports(url), {type: "module"}),
+  },
+  "classic-data-worker-fetch": {
+    path: "/common/security-features/subresource/empty.py",
+    invoker: url =>
+        requestViaDedicatedWorker(dedicatedWorkerUrlThatFetches(url), {}),
+  },
+  "shared-worker": {
+    path: "/common/security-features/subresource/shared-worker.py",
+    invoker: requestViaSharedWorker,
+  },
+
+  "websocket-request": {
+    path: "/stash_responder",
+    invoker: requestViaWebSocket,
+  },
+};
+for (const workletType of ['animation', 'audio', 'layout', 'paint']) {
+  subresourceMap[`worklet-${workletType}-top-level`] = {
+      path: "/common/security-features/subresource/worker.py",
+      invoker: url => requestViaWorklet(workletType, url)
+    };
+  subresourceMap[`worklet-${workletType}-data-import`] = {
+      path: "/common/security-features/subresource/worker.py",
+      invoker: url =>
+          requestViaWorklet(workletType, workerUrlThatImports(url))
+    };
+}
+
+function getRequestURLs(subresourceType, originType, redirectionType) {
+  const key = guid();
+  const value = guid();
+
+  // We use the same stash path for both HTTP/S and WS/S stash requests.
+  const stashPath = encodeURIComponent("/mixed-content");
+
+  const stashEndpoint = "/common/security-features/subresource/xhr.py?key=" +
+                        key + "&path=" + stashPath;
+  return {
+    testUrl:
+      getSubresourceOrigin(originType) +
+        subresourceMap[subresourceType].path +
+        "?redirection=" + encodeURIComponent(redirectionType) +
+        "&action=purge&key=" + key +
+        "&path=" + stashPath,
+    announceUrl: stashEndpoint + "&action=put&value=" + value,
+    assertUrl: stashEndpoint + "&action=take",
+  };
+}
+
 // SanityChecker does nothing in release mode. See sanity-checker.js for debug
 // mode.
 function SanityChecker() {}
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/stylesheet-deleterule-error.html b/third_party/blink/web_tests/external/wpt/css/cssom/stylesheet-deleterule-error.html
new file mode 100644
index 0000000..e01aa01
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/stylesheet-deleterule-error.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSSStyleSheet.prototype.deleteRule error message</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+<link rel="help" href="https://heycam.github.io/webidl/#indexsizeerror">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function () {
+    const styleEl = document.createElement('style');
+    document.head.appendChild(styleEl);
+    try {
+        styleEl.sheet.deleteRule(0);
+        assert_fail("deleteRule on an empty style sheet should throw a RangeError");
+    } catch (e) {
+        assert_equals(e.name,"RangeError");
+        assert_true(e instanceof RangeError);
+    }
+}, 'deleteRule should throw RangeError');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub.html
new file mode 100644
index 0000000..2c230b89
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/common/utils.js></script>
+<body></body>
+<script>
+  test(t => {
+    assert_true(document.createElement('link').relList.supports('prefetch'));
+  }, "Browser supports prefetch.");
+
+  function create_test(host, expected) {
+    async_test(t => {
+      let nonce = token();
+      let key = "prefetch" + nonce;
+
+      let e = document.createElement('link');
+      e.rel = "prefetch";
+      e.href = `https://${host}/fetch/sec-metadata/resources/record-header.py?file=${key}`;
+      e.setAttribute("crossorigin", "crossorigin");
+      e.onload = t.step_func(e => {
+        fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
+          .then(t.step_func(response => response.text()))
+          .then(t.step_func_done(text => assert_header_equals(text, expected)))
+          .catch(t.unreached_func("Fetching and verifying the results should succeed."));
+      });
+      e.onerror = t.unreached_func();
+
+      document.head.appendChild(e);
+    }, `<link rel='prefetch' href='https://${host}/...'>`);
+  }
+
+  create_test("{{host}}:{{ports[https][0]}}", {"dest":"empty", "site":"same-origin", "user":"", "mode": "cors"});
+  create_test("{{hosts[][www]}}:{{ports[https][0]}}", {"dest":"empty", "site":"same-site", "user":"", "mode": "cors"});
+  create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", {"dest":"empty", "site":"cross-site", "user":"", "mode": "cors"});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/preload.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/preload.tentative.https.sub.html
new file mode 100644
index 0000000..2fdf65d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/preload.tentative.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/common/utils.js></script>
+<body></body>
+<script>
+  test(t => {
+    assert_true(document.createElement('link').relList.supports('preload'));
+  }, "Browser supports preload.");
+
+  function create_test(host, as, expected) {
+    async_test(t => {
+      let nonce = token();
+      let key = as + nonce;
+
+      let e = document.createElement('link');
+      e.rel = "preload";
+      e.href = `https://${host}/fetch/sec-metadata/resources/record-header.py?file=${key}`;
+      e.setAttribute("crossorigin", "crossorigin");
+      if (as !== undefined) {
+        e.setAttribute("as", as);
+      }
+      e.onload = e.onerror = t.step_func_done(e => {
+        fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
+          .then(t.step_func(response => response.text()))
+          .then(t.step_func(text => assert_header_equals(text, expected)))
+          .then(t.step_func_done(_ => resolve()))
+          .catch(t.unreached_func());
+      });
+
+      document.head.appendChild(e);
+    }, `<link rel='preload' as='${as}' href='https://${host}/...'>`);
+  }
+
+  let as_tests = [
+    [ "fetch", "empty" ],
+    [ "font", "font" ],
+    [ "image", "image" ],
+    [ "script", "script" ],
+    [ "style", "style" ],
+    [ "track", "track" ],
+  ];
+
+  as_tests.forEach(item => {
+    create_test("{{host}}:{{ports[https][0]}}", item[0], {"dest":item[1], "site":"same-origin", "user":"", "mode": "cors"});
+    create_test("{{hosts[][www]}}:{{ports[https][0]}}", item[0], {"dest":item[1], "site":"same-site", "user":"", "mode": "cors"});
+    create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", item[0], {"dest":item[1], "site":"cross-site", "user":"", "mode": "cors"});
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/record-header.py b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/record-header.py
index f215b016..683c0a6 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/record-header.py
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/record-header.py
@@ -54,6 +54,14 @@
     if key.startswith("serviceworker"):
       response.headers.set("Content-Type", "application/javascript")
 
+    ## Add a valid image Content-Type ##
+    if key.startswith("image"):
+      response.headers.set("Content-Type", "image/png")
+      file = open(os.path.join(request.doc_root, "media", "1x1-green.png"), "r")
+      image = file.read()
+      file.close()
+      return image
+
     ## Return a valid .vtt content for the <track> tag ##
     if key.startswith("track"):
       return "WEBVTT"
diff --git a/third_party/blink/web_tests/external/wpt/mixed-content/generic/mixed-content-test-case.js b/third_party/blink/web_tests/external/wpt/mixed-content/generic/mixed-content-test-case.js
index e7b0baa..6837258 100644
--- a/third_party/blink/web_tests/external/wpt/mixed-content/generic/mixed-content-test-case.js
+++ b/third_party/blink/web_tests/external/wpt/mixed-content/generic/mixed-content-test-case.js
@@ -3,6 +3,39 @@
  * @author burnik@google.com (Kristijan Burnik)
  */
 
+// TODO: This function is currently placed and duplicated at:
+// - mixed-content/generic/mixed-content-test-case.js
+// - referrer-policy/generic/referrer-policy-test-case.js
+// but should be moved to /common/security-features/resources/common.js.
+function getSubresourceOrigin(originType) {
+  const httpProtocol = "http";
+  const httpsProtocol = "https";
+  const wsProtocol = "ws";
+  const wssProtocol = "wss";
+
+  const sameOriginHost = "{{host}}";
+  const crossOriginHost = "{{domains[www1]}}";
+
+  // These values can evaluate to either empty strings or a ":port" string.
+  const httpPort = getNormalizedPort(parseInt("{{ports[http][0]}}", 10));
+  const httpsPort = getNormalizedPort(parseInt("{{ports[https][0]}}", 10));
+  const wsPort = getNormalizedPort(parseInt("{{ports[ws][0]}}", 10));
+  const wssPort = getNormalizedPort(parseInt("{{ports[wss][0]}}", 10));
+
+  const originMap = {
+    "same-https": httpsProtocol + "://" + sameOriginHost + httpsPort,
+    "same-http": httpProtocol + "://" + sameOriginHost + httpPort,
+    "cross-https": httpsProtocol + "://" + crossOriginHost + httpsPort,
+    "cross-http": httpProtocol + "://" + crossOriginHost + httpPort,
+    "same-wss": wssProtocol + "://" + sameOriginHost + wssPort,
+    "same-ws": wsProtocol + "://" + sameOriginHost + wsPort,
+    "cross-wss": wssProtocol + "://" + crossOriginHost + wssPort,
+    "cross-ws": wsProtocol + "://" + crossOriginHost + wsPort,
+  };
+
+  return originMap[originType];
+}
+
 /**
  * MixedContentTestCase exercises all the tests for checking browser behavior
  * when resources regarded as mixed-content are requested. A single run covers
@@ -16,155 +49,43 @@
  * @return {object} Object wrapping the start method used to run the test.
  */
 function MixedContentTestCase(scenario, description, sanityChecker) {
-  const subresourcePath = {
-    "a-tag": "/common/security-features/subresource/document.py",
-    "area-tag": "/common/security-features/subresource/document.py",
-    "beacon-request": "/common/security-features/subresource/empty.py",
-    "fetch-request": "/common/security-features/subresource/xhr.py",
-    "form-tag": "/common/security-features/subresource/empty.py",
-    "iframe-tag": "/common/security-features/subresource/document.py",
-    "img-tag": "/common/security-features/subresource/image.py",
-    "picture-tag": "/common/security-features/subresource/image.py",
-    "script-tag": "/common/security-features/subresource/script.py",
-
-    "worker-request": "/common/security-features/subresource/worker.py",
-    "module-worker-top-level": "/common/security-features/subresource/worker.py",
-    "module-data-worker-import": "/common/security-features/subresource/worker.py",
-
-    "object-tag": "/common/security-features/subresource/empty.py",
-
-    "link-css-tag": "/common/security-features/subresource/empty.py",
-    "link-prefetch-tag": "/common/security-features/subresource/empty.py",
-    "classic-data-worker-fetch": "/common/security-features/subresource/empty.py",
-
-    "xhr-request": "/common/security-features/subresource/xhr.py",
-
-    "audio-tag": "/common/security-features/subresource/audio.py",
-    "video-tag": "/common/security-features/subresource/video.py",
-
-    "websocket-request": "/stash_responder"
+  sanityChecker.checkScenario(scenario, subresourceMap);
+  const originTypeConversion = {
+    "same-host-https": "same-https",
+    "same-host-http": "same-http",
+    "cross-origin-https": "cross-https",
+    "cross-origin-http": "cross-http",
+    "same-host-wss": "same-wss",
+    "same-host-ws": "same-ws",
+    "cross-origin-wss": "cross-wss",
+    "cross-origin-ws": "cross-ws",
   };
-
-  // Mapping all the resource requesting methods to the scenario.
-  var resourceMap = {
-    "a-tag": requestViaAnchor,
-    "area-tag": requestViaArea,
-    "beacon-request": requestViaSendBeacon,
-    "fetch-request": requestViaFetch,
-    "form-tag": requestViaForm,
-    "iframe-tag": requestViaIframe,
-    "img-tag":  requestViaImage,
-    "script-tag": requestViaScript,
-    "worker-request":
-        url => requestViaDedicatedWorker(url),
-    "module-worker-top-level":
-        url => requestViaDedicatedWorker(url, {type: "module"}),
-    "module-data-worker-import":
-        url => requestViaDedicatedWorker(workerUrlThatImports(url), {type: "module"}),
-    "classic-data-worker-fetch":
-        url => requestViaDedicatedWorker(dedicatedWorkerUrlThatFetches(url), {}),
-    "xhr-request": requestViaXhr,
-    "audio-tag": requestViaAudio,
-    "video-tag": requestViaVideo,
-    "picture-tag": requestViaPicture,
-    "object-tag": requestViaObject,
-    "link-css-tag": requestViaLinkStylesheet,
-    "link-prefetch-tag": requestViaLinkPrefetch,
-    "websocket-request": requestViaWebSocket
+  const urls = getRequestURLs(scenario.subresource,
+                              originTypeConversion[scenario.origin],
+                              scenario.redirection);
+  const invoker = subresourceMap[scenario.subresource].invoker;
+  const checkResult = _ => {
+    // Send request to check if the key has been torn down.
+    return xhrRequest(urls.assertUrl)
+      .then(assertResult => {
+          // Now check if the value has been torn down. If it's still there,
+          // we have blocked the request to mixed-content.
+          assert_equals(assertResult.status, scenario.expectation,
+            "The resource request should be '" + scenario.expectation + "'.");
+        });
   };
 
-  for (const workletType of ['animation', 'audio', 'layout', 'paint']) {
-    resourceMap[`worklet-${workletType}-top-level`] =
-      url => requestViaWorklet(workletType, url);
-    subresourcePath[`worklet-${workletType}-top-level`] =
-      "/common/security-features/subresource/worker.py";
-
-    resourceMap[`worklet-${workletType}-data-import`] =
-      url => requestViaWorklet(workletType, workerUrlThatImports(url));
-    subresourcePath[`worklet-${workletType}-data-import`] =
-      "/common/security-features/subresource/worker.py";
-  }
-
-  var httpProtocol = "http";
-  var httpsProtocol = "https";
-  var wsProtocol = "ws";
-  var wssProtocol = "wss";
-
-  var sameOriginHost = location.hostname;
-  var crossOriginHost = "{{domains[www1]}}";
-
-  // These values can evaluate to either empty strings or a ":port" string.
-  var httpPort = getNormalizedPort(parseInt("{{ports[http][0]}}", 10));
-  var httpsPort = getNormalizedPort(parseInt("{{ports[https][0]}}", 10));
-  var wsPort = getNormalizedPort(parseInt("{{ports[ws][0]}}", 10));
-  var wssPort = getNormalizedPort(parseInt("{{ports[wss][0]}}", 10));
-
-  const resourcePath = subresourcePath[scenario.subresource];
-
-  // Map all endpoints to scenario for use in the test.
-  var endpoint = {
-    "same-origin":
-      location.origin + resourcePath,
-    "same-host-https":
-      httpsProtocol + "://" + sameOriginHost + httpsPort + resourcePath,
-    "same-host-http":
-      httpProtocol + "://" + sameOriginHost + httpPort + resourcePath,
-    "cross-origin-https":
-      httpsProtocol + "://" + crossOriginHost + httpsPort + resourcePath,
-    "cross-origin-http":
-      httpProtocol + "://" + crossOriginHost + httpPort + resourcePath,
-    "same-host-wss":
-      wssProtocol + "://" + sameOriginHost + wssPort + resourcePath,
-    "same-host-ws":
-      wsProtocol + "://" + sameOriginHost + wsPort + resourcePath,
-    "cross-origin-wss":
-      wssProtocol + "://" + crossOriginHost + wssPort + resourcePath,
-    "cross-origin-ws":
-      wsProtocol + "://" + crossOriginHost + wsPort + resourcePath
-  };
-
-  sanityChecker.checkScenario(scenario, resourceMap);
-
-  var mixed_content_test = async_test(description);
-
   function runTest() {
-    sanityChecker.setFailTimeout(mixed_content_test);
-
-    var key = guid();
-    var value = guid();
-    // We use the same path for both HTTP/S and WS/S stash requests.
-    var stash_path = encodeURIComponent("/mixed-content");
-    const stashEndpoint = "/common/security-features/subresource/xhr.py?key=" +
-                          key + "&path=" + stash_path;
-    const announceResourceRequestUrl = stashEndpoint + "&action=put&value=" +
-                                       value;
-    const assertResourceRequestUrl = stashEndpoint + "&action=take";
-    const resourceRequestUrl = endpoint[scenario.origin] + "?redirection=" +
-                             scenario.redirection + "&action=purge&key=" + key +
-                             "&path=" + stash_path;
-
-    xhrRequest(announceResourceRequestUrl)
-      .then(mixed_content_test.step_func(_ => {
+    promise_test(() => {
+      return xhrRequest(urls.announceUrl)
         // Send out the real resource request.
         // This should tear down the key if it's not blocked.
-        return resourceMap[scenario.subresource](resourceRequestUrl);
-      }))
-      .then(mixed_content_test.step_func(_ => {
-        // Send request to check if the key has been torn down.
-        return xhrRequest(assertResourceRequestUrl);
-      }))
-      .catch(mixed_content_test.step_func(e => {
-        // When requestResource fails, we also check the key state.
-        return xhrRequest(assertResourceRequestUrl);
-      }))
-      .then(mixed_content_test.step_func_done(response => {
-         // Now check if the value has been torn down. If it's still there,
-         // we have blocked the request to mixed-content.
-         assert_equals(response.status, scenario.expectation,
-           "The resource request should be '" + scenario.expectation + "'.");
-      }));
-
+        .then(_ => invoker(urls.testUrl))
+        // We check the key state, regardless of whether the main request
+        // succeeded or failed.
+        .then(checkResult, checkResult);
+      }, description);
   }  // runTest
 
-  return {start: mixed_content_test.step_func(runTest) };
+  return {start: runTest};
 }  // MixedContentTestCase
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.js b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.js
index 6d570f1..8bdbd39 100644
--- a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.js
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.js
@@ -1,3 +1,36 @@
+// TODO: This function is currently placed and duplicated at:
+// - mixed-content/generic/mixed-content-test-case.js
+// - referrer-policy/generic/referrer-policy-test-case.js
+// but should be moved to /common/security-features/resources/common.js.
+function getSubresourceOrigin(originType) {
+  const httpProtocol = "http";
+  const httpsProtocol = "https";
+  const wsProtocol = "ws";
+  const wssProtocol = "wss";
+
+  const sameOriginHost = "{{host}}";
+  const crossOriginHost = "{{domains[www1]}}";
+
+  // These values can evaluate to either empty strings or a ":port" string.
+  const httpPort = getNormalizedPort(parseInt("{{ports[http][0]}}", 10));
+  const httpsPort = getNormalizedPort(parseInt("{{ports[https][0]}}", 10));
+  const wsPort = getNormalizedPort(parseInt("{{ports[ws][0]}}", 10));
+  const wssPort = getNormalizedPort(parseInt("{{ports[wss][0]}}", 10));
+
+  const originMap = {
+    "same-https": httpsProtocol + "://" + sameOriginHost + httpsPort,
+    "same-http": httpProtocol + "://" + sameOriginHost + httpPort,
+    "cross-https": httpsProtocol + "://" + crossOriginHost + httpsPort,
+    "cross-http": httpProtocol + "://" + crossOriginHost + httpPort,
+    "same-wss": wssProtocol + "://" + sameOriginHost + wssPort,
+    "same-ws": wsProtocol + "://" + sameOriginHost + wsPort,
+    "cross-wss": wssProtocol + "://" + crossOriginHost + wssPort,
+    "cross-ws": wsProtocol + "://" + crossOriginHost + wsPort,
+  };
+
+  return originMap[originType];
+}
+
 // NOTE: This method only strips the fragment and is not in accordance to the
 // recommended draft specification:
 // https://w3c.github.io/webappsec/specs/referrer-policy/#null
@@ -7,14 +40,6 @@
   return url.replace(/#.*$/, "");
 }
 
-function normalizePort(targetPort) {
-  var defaultPorts = [80, 443];
-  var isDefaultPortForProtocol = (defaultPorts.indexOf(targetPort) >= 0);
-
-  return (targetPort == "" || isDefaultPortForProtocol) ?
-          "" : ":" + targetPort;
-}
-
 function ReferrerPolicyTestCase(scenario, testDescription, sanityChecker) {
   // Pass and skip rest of the test if browser does not support fetch.
   if (scenario.subresource == "fetch-request" && !window.fetch) {
@@ -30,120 +55,65 @@
   // This check is A NOOP in release.
   sanityChecker.checkScenario(scenario);
 
-  var subresourceInvoker = {
-    "a-tag": requestViaAnchor,
-    "area-tag": requestViaArea,
-    "fetch-request": requestViaFetch,
-    "iframe-tag": requestViaIframe,
-    "img-tag":  requestViaImageForReferrerPolicy,
-    "script-tag": requestViaScript,
-    "worker-request": url => requestViaDedicatedWorker(url, {}),
-    "module-worker": url => requestViaDedicatedWorker(url, {type: "module"}),
-    "shared-worker": requestViaSharedWorker,
-    "xhr-request": requestViaXhr
+  const originTypeConversion = {
+    "same-origin-http": "same-http",
+    "same-origin-https": "same-https",
+    "cross-origin-http": "cross-http",
+    "cross-origin-https": "cross-https"
   };
-
-  const subresourcePath = {
-    "a-tag": "/common/security-features/subresource/document.py",
-    "area-tag": "/common/security-features/subresource/document.py",
-    "fetch-request": "/common/security-features/subresource/xhr.py",
-    "iframe-tag": "/common/security-features/subresource/document.py",
-    "img-tag": "/common/security-features/subresource/image.py",
-    "script-tag": "/common/security-features/subresource/script.py",
-    "worker-request": "/common/security-features/subresource/worker.py",
-    "module-worker": "/common/security-features/subresource/worker.py",
-    "shared-worker": "/common/security-features/subresource/shared-worker.py",
-    "xhr-request": "/common/security-features/subresource/xhr.py"
-  };
-
-  var referrerUrlResolver = {
-    "omitted": function() {
-      return undefined;
-    },
-    "origin": function() {
-      return self.origin + "/";
-    },
-    "stripped-referrer": function() {
-      return stripUrlForUseAsReferrer(location.toString());
-    }
-  };
-
-  var t = {
-    _scenario: scenario,
-    _testDescription: testDescription,
-    _constructSubresourceUrl: function() {
-      // TODO(kristijanburnik): We should assert that these two domains are
-      // different. E.g. If someone runs the tets over www, this would fail.
-      var domainForOrigin = {
-        "cross-origin":"{{domains[www1]}}",
-        "same-origin": location.hostname
-      };
-
-      // Values obtained and replaced by the wptserve pipeline:
-      // http://wptserve.readthedocs.org/en/latest/pipes.html#built-in-pipes
-      var portForProtocol = {
-        "http": parseInt("{{ports[http][0]}}"),
-        "https": parseInt("{{ports[https][0]}}")
+  const urls = getRequestURLs(
+      scenario.subresource,
+      originTypeConversion[scenario.origin + '-' + scenario.target_protocol],
+      scenario.redirection);
+  const invoker =
+      subresourceMap[scenario.subresource].invokerForReferrerPolicy ||
+      subresourceMap[scenario.subresource].invoker;
+  const checkResult = result => {
+    const referrerUrlResolver = {
+      "omitted": function() {
+        return undefined;
+      },
+      "origin": function() {
+        return self.origin + "/";
+      },
+      "stripped-referrer": function() {
+        return stripUrlForUseAsReferrer(location.toString());
       }
+    };
+    const expectedReferrerUrl =
+      referrerUrlResolver[scenario.referrer_url]();
 
-      var targetPort = portForProtocol[t._scenario.target_protocol];
+    // Check if the result is in valid format. NOOP in release.
+    sanityChecker.checkSubresourceResult(scenario, urls.testUrl, result);
 
-      return t._scenario.target_protocol + "://" +
-             domainForOrigin[t._scenario.origin] +
-             normalizePort(targetPort) +
-             subresourcePath[t._scenario.subresource] +
-             "?redirection=" + t._scenario["redirection"] +
-             "&cache_destroyer=" + (new Date()).getTime();
-    },
+    // Check the reported URL.
+    assert_equals(result.referrer,
+                  expectedReferrerUrl,
+                  "Reported Referrer URL is '" +
+                  scenario.referrer_url + "'.");
+    assert_equals(result.headers.referer,
+                  expectedReferrerUrl,
+                  "Reported Referrer URL from HTTP header is '" +
+                  expectedReferrerUrl + "'");
+  };
 
-    _constructExpectedReferrerUrl: function() {
-      return referrerUrlResolver[t._scenario.referrer_url]();
-    },
-
-    // Returns a promise.
-    _invokeSubresource: function(resourceRequestUrl) {
-      var invoker = subresourceInvoker[t._scenario.subresource];
+  function runTest() {
+    promise_test(_ => {
       // Depending on the delivery method, extend the subresource element with
       // these attributes.
       var elementAttributesForDeliveryMethod = {
-        "attr-referrer":  {referrerPolicy: t._scenario.referrer_policy},
+        "attr-referrer":  {referrerPolicy: scenario.referrer_policy},
         "rel-noreferrer": {rel: "noreferrer"}
       };
-
-      var delivery_method = t._scenario.delivery_method;
-
-      if (delivery_method in elementAttributesForDeliveryMethod) {
-        return invoker(resourceRequestUrl,
-                       elementAttributesForDeliveryMethod[delivery_method],
-                       t._scenario.referrer_policy);
-      } else {
-        return invoker(resourceRequestUrl, {}, t._scenario.referrer_policy);
+      var deliveryMethod = scenario.delivery_method;
+      let elementAttributes = {};
+      if (deliveryMethod in elementAttributesForDeliveryMethod) {
+        elementAttributes = elementAttributesForDeliveryMethod[deliveryMethod];
       }
-    },
-
-    start: function() {
-      promise_test(test => {
-          const resourceRequestUrl = t._constructSubresourceUrl();
-          const expectedReferrerUrl = t._constructExpectedReferrerUrl();
-          return t._invokeSubresource(resourceRequestUrl)
-            .then(result => {
-                // Check if the result is in valid format. NOOP in release.
-                sanityChecker.checkSubresourceResult(
-                    test, t._scenario, resourceRequestUrl, result);
-
-                // Check the reported URL.
-                assert_equals(result.referrer,
-                              expectedReferrerUrl,
-                              "Reported Referrer URL is '" +
-                              t._scenario.referrer_url + "'.");
-                assert_equals(result.headers.referer,
-                              expectedReferrerUrl,
-                              "Reported Referrer URL from HTTP header is '" +
-                              expectedReferrerUrl + "'");
-              });
-        }, t._testDescription);
-    }
+      return invoker(urls.testUrl, elementAttributes, scenario.referrer_policy)
+        .then(checkResult);
+    }, testDescription);
   }
 
-  return t;
+  return {start: runTest};
 }
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/sanity-checker.js b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/sanity-checker.js
index 5c01c36..e296ce9 100644
--- a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/sanity-checker.js
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/sanity-checker.js
@@ -28,22 +28,19 @@
   }, "[ReferrerPolicyTestCase] The test scenario is valid.");
 }
 
-SanityChecker.prototype.checkSubresourceResult = function(test,
-                                                          scenario,
+SanityChecker.prototype.checkSubresourceResult = function(scenario,
                                                           subresourceUrl,
                                                           result) {
-  test.step(function() {
-    assert_equals(Object.keys(result).length, 3);
-    assert_own_property(result, "location");
-    assert_own_property(result, "referrer");
-    assert_own_property(result, "headers");
+  assert_equals(Object.keys(result).length, 3);
+  assert_own_property(result, "location");
+  assert_own_property(result, "referrer");
+  assert_own_property(result, "headers");
 
-    // Skip location check for scripts.
-    if (scenario.subresource == "script-tag")
-      return;
+  // Skip location check for scripts.
+  if (scenario.subresource == "script-tag")
+    return;
 
-    // Sanity check: location of sub-resource matches reported location.
-    assert_equals(result.location, subresourceUrl,
-                  "Subresource reported location.");
-  }, "Running a valid test scenario.");
+  // Sanity check: location of sub-resource matches reported location.
+  assert_equals(result.location, subresourceUrl,
+                "Subresource reported location.");
 };
diff --git a/third_party/blink/web_tests/virtual/outofblink-cors/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub-expected.txt b/third_party/blink/web_tests/virtual/outofblink-cors/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub-expected.txt
new file mode 100644
index 0000000..35b7a194
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/outofblink-cors/external/wpt/fetch/sec-metadata/prefetch.tentative.https.sub-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS Browser supports prefetch.
+PASS <link rel='prefetch' href='https://web-platform.test:8444/...'>
+FAIL <link rel='prefetch' href='https://www.web-platform.test:8444/...'> assert_unreached: Reached unreachable code
+FAIL <link rel='prefetch' href='https://www.not-web-platform.test:8444/...'> assert_unreached: Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn
index 8cc0eac3..6f7e379 100644
--- a/third_party/zlib/BUILD.gn
+++ b/third_party/zlib/BUILD.gn
@@ -67,18 +67,19 @@
 
 if (use_arm_neon_optimizations) {
   config("zlib_arm_crc32_config") {
-    # Restrictions:
-    #  - Disabled for iPhones, as described in DDI0487C_a_armv8_arm:
-    #     "All implementations of the ARMv8.1 architecture are required to
-    #      implement the CRC32* instructions. These are optional in ARMv8.0."
-    #  - Fuchsia just added a syscall for feature detection.
-    # TODO(cavalcantii): crbug.com/810125.
-    if (!is_ios && !is_fuchsia) {
+    # Disabled for iPhone, as described in DDI0487C_a_armv8_arm:
+    #  "All implementations of the ARMv8.1 architecture are required to
+    #   implement the CRC32* instructions. These are optional in ARMv8.0."
+    if (!is_ios) {
       defines = [ "CRC32_ARMV8_CRC32" ]
       if (is_android) {
         defines += [ "ARMV8_OS_ANDROID" ]
       } else if (is_linux || is_chromeos) {
         defines += [ "ARMV8_OS_LINUX" ]
+      } else if (is_fuchsia) {
+        defines += [ "ARMV8_OS_FUCHSIA" ]
+      } else {
+        assert(false, "Unsupported ARM OS")
       }
     }
   }
@@ -86,7 +87,7 @@
   source_set("zlib_arm_crc32") {
     visibility = [ ":*" ]
 
-    if (is_clang && (!is_ios && !is_fuchsia)) {
+    if (is_clang && !is_ios) {
       include_dirs = [ "." ]
 
       if (is_android) {
@@ -95,6 +96,8 @@
           deps = [
             "//third_party/android_sdk:cpu_features",
           ]
+        } else {
+          assert(false, "CPU detection requires the Android NDK")
         }
       }
 
@@ -149,8 +152,7 @@
 
     if (use_arm_neon_optimizations && !is_debug) {
       # Here we trade better performance on newer/bigger ARMv8 cores
-      # for less perf on ARMv7. For details, check:
-      # https://bugs.chromium.org/p/chromium/issues/detail?id=772870#c40
+      # for less perf on ARMv7, per crbug.com/772870#c40
       configs -= [ "//build/config/compiler:default_optimization" ]
       configs += [ "//build/config/compiler:optimize_speed" ]
     }
diff --git a/third_party/zlib/arm_features.c b/third_party/zlib/arm_features.c
index f91897d..73aca420 100644
--- a/third_party/zlib/arm_features.c
+++ b/third_party/zlib/arm_features.c
@@ -21,6 +21,10 @@
 #elif defined(ARMV8_OS_LINUX)
 #include <asm/hwcap.h>
 #include <sys/auxv.h>
+#elif defined(ARMV8_OS_FUCHSIA)
+#include <zircon/features.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
 #else
 #error arm_features.c ARM feature detection in not defined for your platform
 #endif
@@ -57,8 +61,14 @@
     unsigned long features = getauxval(AT_HWCAP2);
     arm_cpu_enable_crc32 = !!(features & HWCAP2_CRC32);
     arm_cpu_enable_pmull = !!(features & HWCAP2_PMULL);
+#elif defined(ARMV8_OS_FUCHSIA)
+    uint32_t features;
+    zx_status_t rc = zx_system_get_features(ZX_FEATURE_KIND_CPU, &features);
+    if (rc != ZX_OK || (features & ZX_ARM64_FEATURE_ISA_ASIMD) == 0)
+        return;  /* Report nothing if ASIMD(NEON) is missing */
+    arm_cpu_enable_crc32 = !!(features & ZX_ARM64_FEATURE_ISA_CRC32);
+    arm_cpu_enable_pmull = !!(features & ZX_ARM64_FEATURE_ISA_PMULL);
 #endif
-    /* TODO(crbug.com/810125): add ARMV8_OS_ZIRCON support for fucshia */
 }
 
 #else /* _MSC_VER */
diff --git a/tools/grit/grit_rule.gni b/tools/grit/grit_rule.gni
index 6e8e3d0..0e1dc9f 100644
--- a/tools/grit/grit_rule.gni
+++ b/tools/grit/grit_rule.gni
@@ -2,8 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Instantiate grit. This will produce a script target to run grit, and a
-# static library that compiles the .cc files.
+# Instantiate grit. This will produce a script target to run grit (named
+# ${target_name}_grit), and a static library that compiles the .cc files.
+#
+# In general, code should depend on the static library. However, if the
+# generated files are only processed by other actions to generate other
+# files, it is possible to depend on the script target directly.
 #
 # Parameters
 #
@@ -94,7 +98,6 @@
 import("//build/config/ui.gni")
 import("//build/toolchain/gcc_toolchain.gni")  # For enable_resource_whitelist_generation
 import("//third_party/closure_compiler/closure_args.gni")
-
 if (enable_resource_whitelist_generation) {
   assert(target_os == "android" || target_os == "win",
          "unsupported platform for resource whitelist generation")
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 69e38845..28782f2 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4300,7 +4300,7 @@
   <int value="9" label="OBSOLETE_RWH_FOCUS"/>
   <int value="10" label="OBSOLETE_RWH_BLUR"/>
   <int value="11" label="RWH_SHARED_BITMAP"/>
-  <int value="12" label="RWH_BAD_ACK_MESSAGE"/>
+  <int value="12" label="OBSOLETE_RWH_BAD_ACK_MESSAGE"/>
   <int value="13" label="OBSOLETE_RWHVA_SHARED_MEMORY"/>
   <int value="14" label="SERVICE_WORKER_BAD_URL"/>
   <int value="15" label="OBSOLETE_WC_INVALID_FRAME_SOURCE"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index fd78724..b14fc83d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -56405,6 +56405,28 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Memory.Experimental.UserLevelMemoryPressureSignal.RendererPrivateMemoryFootprintAfter"
+    units="MB" expires_after="2019-09-30">
+  <owner>keishi@chromium.org</owner>
+  <owner>tasak@chromium.org</owner>
+  <summary>
+    The renderer process' private memory footprint 10 seconds after a memory
+    pressure signal is generated.
+  </summary>
+</histogram>
+
+<histogram
+    name="Memory.Experimental.UserLevelMemoryPressureSignal.RendererPrivateMemoryFootprintBefore"
+    units="MB" expires_after="2019-09-30">
+  <owner>keishi@chromium.org</owner>
+  <owner>tasak@chromium.org</owner>
+  <summary>
+    The renderer process' private memory footprint right before a memory
+    pressure signal is generated.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Memory.Experimental.Utility2" units="MB"
     expires_after="2019-12-31">
 <!-- Name completed by histogram_suffixes name="ProcessMemoryAllocator2" -->
diff --git a/ui/file_manager/file_manager/background/js/drive_sync_handler.js b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
index 1c624dd..988c2e0f 100644
--- a/ui/file_manager/file_manager/background/js/drive_sync_handler.js
+++ b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
@@ -4,93 +4,90 @@
 
 /**
  * Handler of the background page for the Drive sync events.
- *
- * @constructor
- * @struct
  * @implements {DriveSyncHandler}
- * @extends {cr.EventTarget}
  */
-function DriveSyncHandlerImpl(progressCenter) {
-  /**
-   * Progress center to submit the progressing item.
-   * @type {ProgressCenter}
-   * @const
-   * @private
-   */
-  this.progressCenter_ = progressCenter;
+class DriveSyncHandlerImpl extends cr.EventTarget {
+  /** @param {ProgressCenter} progressCenter */
+  constructor(progressCenter) {
+    super();
 
-  /**
-   * Predefined error ID for out of quota messages.
-   * @type {number}
-   * @const
-   * @private
-   */
-  this.driveErrorIdOutOfQuota_ = 1;
+    /**
+     * Progress center to submit the progressing item.
+     * @type {ProgressCenter}
+     * @const
+     * @private
+     */
+    this.progressCenter_ = progressCenter;
 
-  /**
-   * Maximum reserved ID for predefined errors.
-   * @type {number}
-   * @const
-   * @private
-   */
-  this.driveErrorIdMax_ = this.driveErrorIdOutOfQuota_;
+    /**
+     * Predefined error ID for out of quota messages.
+     * @type {number}
+     * @const
+     * @private
+     */
+    this.driveErrorIdOutOfQuota_ = 1;
 
-  /**
-   * Counter for error ID.
-   * @type {number}
-   * @private
-   */
-  this.errorIdCounter_ = this.driveErrorIdMax_ + 1;
+    /**
+     * Maximum reserved ID for predefined errors.
+     * @type {number}
+     * @const
+     * @private
+     */
+    this.driveErrorIdMax_ = this.driveErrorIdOutOfQuota_;
 
-  /**
-   * Progress center item.
-   * @type {ProgressCenterItem}
-   * @const
-   * @private
-   */
-  this.item_ = new ProgressCenterItem();
-  this.item_.id = 'drive-sync';
+    /**
+     * Counter for error ID.
+     * @type {number}
+     * @private
+     */
+    this.errorIdCounter_ = this.driveErrorIdMax_ + 1;
 
-  /**
-   * If the property is true, this item is syncing.
-   * @type {boolean}
-   * @private
-   */
-  this.syncing_ = false;
+    /**
+     * Progress center item.
+     * @type {ProgressCenterItem}
+     * @const
+     * @private
+     */
+    this.item_ = new ProgressCenterItem();
+    this.item_.id = 'drive-sync';
 
-  /**
-   * Whether the sync is disabled on cellular network or not.
-   * @type {boolean}
-   * @private
-   */
-  this.cellularDisabled_ = false;
+    /**
+     * If the property is true, this item is syncing.
+     * @type {boolean}
+     * @private
+     */
+    this.syncing_ = false;
 
-  /**
-   * Async queue.
-   * @type {AsyncUtil.Queue}
-   * @const
-   * @private
-   */
-  this.queue_ = new AsyncUtil.Queue();
+    /**
+     * Whether the sync is disabled on cellular network or not.
+     * @type {boolean}
+     * @private
+     */
+    this.cellularDisabled_ = false;
 
-  // Register events.
-  chrome.fileManagerPrivate.onFileTransfersUpdated.addListener(
-      this.onFileTransfersUpdated_.bind(this));
-  chrome.fileManagerPrivate.onDriveSyncError.addListener(
-      this.onDriveSyncError_.bind(this));
-  chrome.notifications.onButtonClicked.addListener(
-      this.onNotificationButtonClicked_.bind(this));
-  chrome.fileManagerPrivate.onPreferencesChanged.addListener(
-      this.onPreferencesChanged_.bind(this));
-  chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
-      this.onDriveConnectionStatusChanged_.bind(this));
+    /**
+     * Async queue.
+     * @type {AsyncUtil.Queue}
+     * @const
+     * @private
+     */
+    this.queue_ = new AsyncUtil.Queue();
 
-  // Set initial values.
-  this.onPreferencesChanged_();
-}
+    // Register events.
+    chrome.fileManagerPrivate.onFileTransfersUpdated.addListener(
+        this.onFileTransfersUpdated_.bind(this));
+    chrome.fileManagerPrivate.onDriveSyncError.addListener(
+        this.onDriveSyncError_.bind(this));
+    chrome.notifications.onButtonClicked.addListener(
+        this.onNotificationButtonClicked_.bind(this));
+    chrome.fileManagerPrivate.onPreferencesChanged.addListener(
+        this.onPreferencesChanged_.bind(this));
+    chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
+        this.onDriveConnectionStatusChanged_.bind(this));
 
-DriveSyncHandlerImpl.prototype = /** @struct */ {
-  __proto__: cr.EventTarget.prototype,
+    // Set initial values.
+    this.onPreferencesChanged_();
+  }
 
   /**
    * @return {boolean} Whether the handler is syncing items or not.
@@ -98,7 +95,199 @@
   get syncing() {
     return this.syncing_;
   }
-};
+
+  /**
+   * Returns the completed event name.
+   * @return {string}
+   */
+  getCompletedEventName() {
+    return DriveSyncHandlerImpl.DRIVE_SYNC_COMPLETED_EVENT;
+  }
+
+  /**
+   * Returns whether the Drive sync is currently suppressed or not.
+   * @return {boolean}
+   */
+  isSyncSuppressed() {
+    return navigator.connection.type === 'cellular' && this.cellularDisabled_;
+  }
+
+  /**
+   * Shows a notification that Drive sync is disabled on cellular networks.
+   */
+  showDisabledMobileSyncNotification() {
+    chrome.notifications.create(
+        DriveSyncHandlerImpl.DISABLED_MOBILE_SYNC_NOTIFICATION_ID_, {
+          type: 'basic',
+          title: chrome.runtime.getManifest().name,
+          message: str('DISABLED_MOBILE_SYNC_NOTIFICATION_MESSAGE'),
+          iconUrl: chrome.runtime.getURL('/common/images/icon96.png'),
+          buttons:
+              [{title: str('DISABLED_MOBILE_SYNC_NOTIFICATION_ENABLE_BUTTON')}]
+        },
+        () => {});
+  }
+
+  /**
+   * Handles file transfer updated events.
+   * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
+   *     status.
+   * @private
+   */
+  onFileTransfersUpdated_(status) {
+    switch (status.transferState) {
+      case 'in_progress':
+        this.updateItem_(status);
+        break;
+      case 'completed':
+      case 'failed':
+        if ((status.hideWhenZeroJobs && status.num_total_jobs === 0) ||
+            (!status.hideWhenZeroJobs && status.num_total_jobs === 1)) {
+          this.removeItem_(status);
+        }
+        break;
+      default:
+        throw new Error(
+            'Invalid transfer state: ' + status.transferState + '.');
+    }
+  }
+
+  /**
+   * Updates the item involved with the given status.
+   * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
+   *     status.
+   * @private
+   */
+  updateItem_(status) {
+    this.queue_.run(callback => {
+      window.webkitResolveLocalFileSystemURL(
+          status.fileUrl,
+          entry => {
+            this.item_.state = ProgressItemState.PROGRESSING;
+            this.item_.type = ProgressItemType.SYNC;
+            this.item_.quiet = true;
+            this.syncing_ = true;
+            if (status.num_total_jobs > 1) {
+              this.item_.message =
+                  strf('SYNC_FILE_NUMBER', status.num_total_jobs);
+            } else {
+              this.item_.message = strf('SYNC_FILE_NAME', entry.name);
+            }
+            this.item_.progressValue = status.processed || 0;
+            this.item_.progressMax = status.total || 0;
+            this.progressCenter_.updateItem(this.item_);
+            callback();
+          },
+          error => {
+            console.warn(
+                'Resolving URL ' + status.fileUrl + ' is failed: ', error);
+            callback();
+          });
+    });
+  }
+
+  /**
+   * Removes the item involved with the given status.
+   * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
+   *     status.
+   * @private
+   */
+  removeItem_(status) {
+    this.queue_.run(callback => {
+      this.item_.state = status.transferState === 'completed' ?
+          ProgressItemState.COMPLETED :
+          ProgressItemState.CANCELED;
+      this.progressCenter_.updateItem(this.item_);
+      this.syncing_ = false;
+      this.dispatchEvent(new Event(this.getCompletedEventName()));
+      callback();
+    });
+  }
+
+  /**
+   * Handles drive's sync errors.
+   * @param {chrome.fileManagerPrivate.DriveSyncErrorEvent} event Drive sync
+   * error event.
+   * @private
+   */
+  onDriveSyncError_(event) {
+    window.webkitResolveLocalFileSystemURL(event.fileUrl, entry => {
+      const item = new ProgressCenterItem();
+      item.type = ProgressItemType.SYNC;
+      item.quiet = true;
+      item.state = ProgressItemState.ERROR;
+      switch (event.type) {
+        case 'delete_without_permission':
+          item.message =
+              strf('SYNC_DELETE_WITHOUT_PERMISSION_ERROR', entry.name);
+          break;
+        case 'service_unavailable':
+          item.message = str('SYNC_SERVICE_UNAVAILABLE_ERROR');
+          break;
+        case 'no_server_space':
+          item.message = strf('SYNC_NO_SERVER_SPACE', entry.name);
+          // This error will reappear every time sync is retried, so we use a
+          // fixed ID to avoid spamming the user.
+          item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX +
+              this.driveErrorIdOutOfQuota_;
+          break;
+        case 'misc':
+          item.message = strf('SYNC_MISC_ERROR', entry.name);
+          break;
+      }
+      if (!item.id) {
+        item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX +
+            (this.errorIdCounter_++);
+      }
+      this.progressCenter_.updateItem(item);
+    });
+  }
+
+  /**
+   * Handles notification's button click.
+   * @param {string} notificationId Notification ID.
+   * @param {number} buttonIndex Index of the button.
+   * @private
+   */
+  onNotificationButtonClicked_(notificationId, buttonIndex) {
+    const expectedId =
+        DriveSyncHandlerImpl.DISABLED_MOBILE_SYNC_NOTIFICATION_ID_;
+    if (notificationId !== expectedId) {
+      return;
+    }
+    chrome.notifications.clear(notificationId, () => {});
+    chrome.fileManagerPrivate.setPreferences({cellularDisabled: false});
+  }
+
+  /**
+   * Handles preferences change.
+   * @private
+   */
+  onPreferencesChanged_() {
+    chrome.fileManagerPrivate.getPreferences(pref => {
+      this.cellularDisabled_ = pref.cellularDisabled;
+    });
+  }
+
+  /**
+   * Handles connection state change.
+   * @private
+   */
+  onDriveConnectionStatusChanged_() {
+    chrome.fileManagerPrivate.getDriveConnectionState((state) => {
+      // If offline, hide any sync progress notifications. When online again,
+      // the Drive sync client may retry syncing and trigger
+      // onFileTransfersUpdated events, causing it to be shown again.
+      if (state.type == 'offline' && state.reason == 'no_network' &&
+          this.syncing_) {
+        this.syncing_ = false;
+        this.item_.state = ProgressItemState.CANCELED;
+        this.progressCenter_.updateItem(this.item_);
+        this.dispatchEvent(new Event(this.getCompletedEventName()));
+      }
+    });
+  }
+}
 
 /**
  * Completed event name.
@@ -109,22 +298,6 @@
 DriveSyncHandlerImpl.DRIVE_SYNC_COMPLETED_EVENT = 'completed';
 
 /**
- * Returns the completed event name.
- * @return {string}
- */
-DriveSyncHandlerImpl.prototype.getCompletedEventName = () => {
-  return DriveSyncHandlerImpl.DRIVE_SYNC_COMPLETED_EVENT;
-};
-
-/**
- * Returns whether the Drive sync is currently suppressed or not.
- * @return {boolean}
- */
-DriveSyncHandlerImpl.prototype.isSyncSuppressed = function() {
-  return navigator.connection.type === 'cellular' && this.cellularDisabled_;
-};
-
-/**
  * Notification ID of the disabled mobile sync notification.
  * @type {string}
  * @private
@@ -133,93 +306,6 @@
 DriveSyncHandlerImpl.DISABLED_MOBILE_SYNC_NOTIFICATION_ID_ =
     'disabled-mobile-sync';
 
-/**
- * Shows a notification that Drive sync is disabled on cellular networks.
- */
-DriveSyncHandlerImpl.prototype.showDisabledMobileSyncNotification = () => {
-  chrome.notifications.create(
-      DriveSyncHandlerImpl.DISABLED_MOBILE_SYNC_NOTIFICATION_ID_, {
-        type: 'basic',
-        title: chrome.runtime.getManifest().name,
-        message: str('DISABLED_MOBILE_SYNC_NOTIFICATION_MESSAGE'),
-        iconUrl: chrome.runtime.getURL('/common/images/icon96.png'),
-        buttons:
-            [{title: str('DISABLED_MOBILE_SYNC_NOTIFICATION_ENABLE_BUTTON')}]
-      },
-      () => {});
-};
-
-/**
- * Handles file transfer updated events.
- * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer status.
- * @private
- */
-DriveSyncHandlerImpl.prototype.onFileTransfersUpdated_ = function(status) {
-  switch (status.transferState) {
-    case 'in_progress':
-      this.updateItem_(status);
-      break;
-    case 'completed':
-    case 'failed':
-      if ((status.hideWhenZeroJobs && status.num_total_jobs === 0) ||
-          (!status.hideWhenZeroJobs && status.num_total_jobs === 1)) {
-        this.removeItem_(status);
-      }
-      break;
-    default:
-      throw new Error('Invalid transfer state: ' + status.transferState + '.');
-  }
-};
-
-/**
- * Updates the item involved with the given status.
- * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer status.
- * @private
- */
-DriveSyncHandlerImpl.prototype.updateItem_ = function(status) {
-  this.queue_.run(callback => {
-    window.webkitResolveLocalFileSystemURL(
-        status.fileUrl,
-        entry => {
-          this.item_.state = ProgressItemState.PROGRESSING;
-          this.item_.type = ProgressItemType.SYNC;
-          this.item_.quiet = true;
-          this.syncing_ = true;
-          if (status.num_total_jobs > 1) {
-            this.item_.message =
-                strf('SYNC_FILE_NUMBER', status.num_total_jobs);
-          } else {
-            this.item_.message = strf('SYNC_FILE_NAME', entry.name);
-          }
-          this.item_.progressValue = status.processed || 0;
-          this.item_.progressMax = status.total || 0;
-          this.progressCenter_.updateItem(this.item_);
-          callback();
-        },
-        error => {
-          console.warn(
-              'Resolving URL ' + status.fileUrl + ' is failed: ', error);
-          callback();
-        });
-  });
-};
-
-/**
- * Removes the item involved with the given status.
- * @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer status.
- * @private
- */
-DriveSyncHandlerImpl.prototype.removeItem_ = function(status) {
-  this.queue_.run(callback => {
-    this.item_.state = status.transferState === 'completed' ?
-        ProgressItemState.COMPLETED :
-        ProgressItemState.CANCELED;
-    this.progressCenter_.updateItem(this.item_);
-    this.syncing_ = false;
-    this.dispatchEvent(new Event(this.getCompletedEventName()));
-    callback();
-  });
-};
 
 /**
  * Drive sync error prefix.
@@ -228,87 +314,3 @@
  * @const
  */
 DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX = 'drive-sync-error-';
-
-/**
- * Handles drive's sync errors.
- * @param {chrome.fileManagerPrivate.DriveSyncErrorEvent} event Drive sync
- * error event.
- * @private
- */
-DriveSyncHandlerImpl.prototype.onDriveSyncError_ = function(event) {
-  window.webkitResolveLocalFileSystemURL(event.fileUrl, entry => {
-    const item = new ProgressCenterItem();
-    item.type = ProgressItemType.SYNC;
-    item.quiet = true;
-    item.state = ProgressItemState.ERROR;
-    switch (event.type) {
-      case 'delete_without_permission':
-        item.message = strf('SYNC_DELETE_WITHOUT_PERMISSION_ERROR', entry.name);
-        break;
-      case 'service_unavailable':
-        item.message = str('SYNC_SERVICE_UNAVAILABLE_ERROR');
-        break;
-      case 'no_server_space':
-        item.message = strf('SYNC_NO_SERVER_SPACE', entry.name);
-        // This error will reappear every time sync is retried, so we use a
-        // fixed ID to avoid spamming the user.
-        item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX +
-            this.driveErrorIdOutOfQuota_;
-        break;
-      case 'misc':
-        item.message = strf('SYNC_MISC_ERROR', entry.name);
-        break;
-    }
-    if (!item.id) {
-      item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX +
-          (this.errorIdCounter_++);
-    }
-    this.progressCenter_.updateItem(item);
-  });
-};
-
-/**
- * Handles notification's button click.
- * @param {string} notificationId Notification ID.
- * @param {number} buttonIndex Index of the button.
- * @private
- */
-DriveSyncHandlerImpl.prototype.onNotificationButtonClicked_ =
-    (notificationId, buttonIndex) => {
-      const expectedId =
-          DriveSyncHandlerImpl.DISABLED_MOBILE_SYNC_NOTIFICATION_ID_;
-      if (notificationId !== expectedId) {
-        return;
-      }
-      chrome.notifications.clear(notificationId, () => {});
-      chrome.fileManagerPrivate.setPreferences({cellularDisabled: false});
-    };
-
-/**
- * Handles preferences change.
- * @private
- */
-DriveSyncHandlerImpl.prototype.onPreferencesChanged_ = function() {
-  chrome.fileManagerPrivate.getPreferences(pref => {
-    this.cellularDisabled_ = pref.cellularDisabled;
-  });
-};
-
-/**
- * Handles connection state change.
- * @private
- */
-DriveSyncHandlerImpl.prototype.onDriveConnectionStatusChanged_ = function() {
-  chrome.fileManagerPrivate.getDriveConnectionState((state) => {
-    // If offline, hide any sync progress notifications. When online again, the
-    // Drive sync client may retry syncing and trigger onFileTransfersUpdated
-    // events, causing it to be shown again.
-    if (state.type == 'offline' && state.reason == 'no_network' &&
-        this.syncing_) {
-      this.syncing_ = false;
-      this.item_.state = ProgressItemState.CANCELED;
-      this.progressCenter_.updateItem(this.item_);
-      this.dispatchEvent(new Event(this.getCompletedEventName()));
-    }
-  });
-};
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_impl.js b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
index c8c8856..9bf335c 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_impl.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
@@ -4,516 +4,514 @@
 
 /**
  * VolumeManager is responsible for tracking list of mounted volumes.
- *
- * @constructor
  * @implements {VolumeManager}
- * @extends {cr.EventTarget}
  */
-function VolumeManagerImpl() {
-  /** @override */
-  this.volumeInfoList = new VolumeInfoListImpl();
+class VolumeManagerImpl extends cr.EventTarget {
+  constructor() {
+    super();
+
+    /** @override */
+    this.volumeInfoList = new VolumeInfoListImpl();
+
+    /**
+     * The list of archives requested to mount. We will show contents once
+     * archive is mounted, but only for mounts from within this filebrowser tab.
+     * @type {Object<Object>}
+     * @private
+     */
+    this.requests_ = {};
+
+    /**
+     * Queue for mounting.
+     * @type {AsyncUtil.Queue}
+     * @private
+     */
+    this.mountQueue_ = new AsyncUtil.Queue();
+
+    // The status should be merged into VolumeManager.
+    // TODO(hidehiko): Remove them after the migration.
+    /**
+     * Connection state of the Drive.
+     * @type {VolumeManagerCommon.DriveConnectionState}
+     * @private
+     */
+    this.driveConnectionState_ = {
+      type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
+      reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE,
+      hasCellularNetworkAccess: false
+    };
+
+    chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
+        this.onDriveConnectionStatusChanged_.bind(this));
+    this.onDriveConnectionStatusChanged_();
+  }
 
   /**
-   * The list of archives requested to mount. We will show contents once
-   * archive is mounted, but only for mounts from within this filebrowser tab.
-   * @type {Object<Object>}
+   * Invoked when the drive connection status is changed.
    * @private
    */
-  this.requests_ = {};
-
-  /**
-   * Queue for mounting.
-   * @type {AsyncUtil.Queue}
-   * @private
-   */
-  this.mountQueue_ = new AsyncUtil.Queue();
-
-  // The status should be merged into VolumeManager.
-  // TODO(hidehiko): Remove them after the migration.
-  /**
-   * Connection state of the Drive.
-   * @type {VolumeManagerCommon.DriveConnectionState}
-   * @private
-   */
-  this.driveConnectionState_ = {
-    type: VolumeManagerCommon.DriveConnectionType.OFFLINE,
-    reason: VolumeManagerCommon.DriveConnectionReason.NO_SERVICE,
-    hasCellularNetworkAccess: false
-  };
-
-  chrome.fileManagerPrivate.onDriveConnectionStatusChanged.addListener(
-      this.onDriveConnectionStatusChanged_.bind(this));
-  this.onDriveConnectionStatusChanged_();
-}
-
-/**
- * Invoked when the drive connection status is changed.
- * @private
- */
-VolumeManagerImpl.prototype.onDriveConnectionStatusChanged_ = function() {
-  chrome.fileManagerPrivate.getDriveConnectionState(state => {
-    // TODO(crbug.com/931971): Convert private API to use enum.
-    this.driveConnectionState_ =
-        /** @type {VolumeManagerCommon.DriveConnectionState} */ (state);
-    cr.dispatchSimpleEvent(this, 'drive-connection-changed');
-  });
-};
-
-/** @override */
-VolumeManagerImpl.prototype.getDriveConnectionState = function() {
-  return this.driveConnectionState_;
-};
-
-/**
- * VolumeManager extends cr.EventTarget.
- */
-VolumeManagerImpl.prototype.__proto__ = cr.EventTarget.prototype;
-
-/**
- * Adds new volume info from the given volumeMetadata. If the corresponding
- * volume info has already been added, the volumeMetadata is ignored.
- * @param {!chrome.fileManagerPrivate.VolumeMetadata} volumeMetadata
- * @return {!Promise<!VolumeInfo>}
- * @private
- */
-VolumeManagerImpl.prototype.addVolumeMetadata_ = function(volumeMetadata) {
-  return volumeManagerUtil.createVolumeInfo(volumeMetadata)
-      .then(
-          /**
-           * @param {!VolumeInfo} volumeInfo
-           * @return {!VolumeInfo}
-           */
-          volumeInfo => {
-            // We don't show Downloads and Drive on volume list if they have
-            // mount error, since users can do nothing in this situation. We
-            // show Removable and Provided volumes regardless of mount error so
-            // that users can unmount or format the volume.
-            // TODO(fukino): Once the Files app gets ready, show erroneous Drive
-            // volume so that users can see auth warning banner on the volume.
-            // crbug.com/517772.
-            let shouldShow = true;
-            switch (volumeInfo.volumeType) {
-              case VolumeManagerCommon.VolumeType.DOWNLOADS:
-              case VolumeManagerCommon.VolumeType.DRIVE:
-                shouldShow = !!volumeInfo.fileSystem;
-                break;
-            }
-            if (!shouldShow) {
-              return volumeInfo;
-            }
-            if (this.volumeInfoList.findIndex(volumeInfo.volumeId) === -1) {
-              this.volumeInfoList.add(volumeInfo);
-
-              // Update the network connection status, because until the drive
-              // is initialized, the status is set to not ready.
-              // TODO(mtomasz): The connection status should be migrated into
-              // chrome.fileManagerPrivate.VolumeMetadata.
-              if (volumeMetadata.volumeType ===
-                  VolumeManagerCommon.VolumeType.DRIVE) {
-                this.onDriveConnectionStatusChanged_();
-              }
-            } else if (
-                volumeMetadata.volumeType ===
-                VolumeManagerCommon.VolumeType.REMOVABLE) {
-              // Update for remounted USB external storage, because they were
-              // remounted to switch read-only policy.
-              this.volumeInfoList.add(volumeInfo);
-            }
-            return volumeInfo;
-          });
-};
-
-/**
- * Initializes mount points.
- * @param {function()} callback Called upon the completion of the
- *     initialization.
- * @private
- */
-VolumeManagerImpl.prototype.initialize_ = function(callback) {
-  chrome.fileManagerPrivate.onMountCompleted.addListener(
-      this.onMountCompleted_.bind(this));
-  console.warn('Requesting volume list.');
-  chrome.fileManagerPrivate.getVolumeMetadataList(volumeMetadataList => {
-    console.warn(
-        'Volume list fetched with: ' + volumeMetadataList.length + ' items.');
-    // We must subscribe to the mount completed event in the callback of
-    // getVolumeMetadataList. crbug.com/330061.
-    // But volumes reported by onMountCompleted events must be added after the
-    // volumes in the volumeMetadataList are mounted. crbug.com/135477.
-    this.mountQueue_.run(inCallback => {
-      // Create VolumeInfo for each volume.
-      Promise
-          .all(volumeMetadataList.map(volumeMetadata => {
-            console.warn('Initializing volume: ' + volumeMetadata.volumeId);
-            return this.addVolumeMetadata_(volumeMetadata).then(volumeInfo => {
-              console.warn('Initialized volume: ' + volumeInfo.volumeId);
-            });
-          }))
-          .then(() => {
-            console.warn('Initialized all volumes.');
-            // Call the callback of the initialize function.
-            callback();
-            // Call the callback of AsyncQueue. Maybe it invokes callbacks
-            // registered by mountCompleted events.
-            inCallback();
-          });
+  onDriveConnectionStatusChanged_() {
+    chrome.fileManagerPrivate.getDriveConnectionState(state => {
+      // TODO(crbug.com/931971): Convert private API to use enum.
+      this.driveConnectionState_ =
+          /** @type {VolumeManagerCommon.DriveConnectionState} */ (state);
+      cr.dispatchSimpleEvent(this, 'drive-connection-changed');
     });
-  });
-};
+  }
 
-/**
- * Event handler called when some volume was mounted or unmounted.
- * @param {chrome.fileManagerPrivate.MountCompletedEvent} event Received event.
- * @private
- */
-VolumeManagerImpl.prototype.onMountCompleted_ = function(event) {
-  this.mountQueue_.run(callback => {
-    switch (event.eventType) {
-      case 'mount':
-        var requestKey = this.makeRequestKey_(
-            'mount', event.volumeMetadata.sourcePath || '');
+  /** @override */
+  getDriveConnectionState() {
+    return this.driveConnectionState_;
+  }
 
-        if (event.status === 'success' ||
-            event.status ===
-                VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM ||
-            event.status ===
-                VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
-          this.addVolumeMetadata_(event.volumeMetadata).then(volumeInfo => {
-            this.finishRequest_(requestKey, event.status, volumeInfo);
+  /**
+   * Adds new volume info from the given volumeMetadata. If the corresponding
+   * volume info has already been added, the volumeMetadata is ignored.
+   * @param {!chrome.fileManagerPrivate.VolumeMetadata} volumeMetadata
+   * @return {!Promise<!VolumeInfo>}
+   * @private
+   */
+  addVolumeMetadata_(volumeMetadata) {
+    return volumeManagerUtil.createVolumeInfo(volumeMetadata)
+        .then(
+            /**
+             * @param {!VolumeInfo} volumeInfo
+             * @return {!VolumeInfo}
+             */
+            volumeInfo => {
+              // We don't show Downloads and Drive on volume list if they have
+              // mount error, since users can do nothing in this situation. We
+              // show Removable and Provided volumes regardless of mount error
+              // so that users can unmount or format the volume.
+              // TODO(fukino): Once the Files app gets ready, show erroneous
+              // Drive volume so that users can see auth warning banner on the
+              // volume. crbug.com/517772.
+              let shouldShow = true;
+              switch (volumeInfo.volumeType) {
+                case VolumeManagerCommon.VolumeType.DOWNLOADS:
+                case VolumeManagerCommon.VolumeType.DRIVE:
+                  shouldShow = !!volumeInfo.fileSystem;
+                  break;
+              }
+              if (!shouldShow) {
+                return volumeInfo;
+              }
+              if (this.volumeInfoList.findIndex(volumeInfo.volumeId) === -1) {
+                this.volumeInfoList.add(volumeInfo);
+
+                // Update the network connection status, because until the drive
+                // is initialized, the status is set to not ready.
+                // TODO(mtomasz): The connection status should be migrated into
+                // chrome.fileManagerPrivate.VolumeMetadata.
+                if (volumeMetadata.volumeType ===
+                    VolumeManagerCommon.VolumeType.DRIVE) {
+                  this.onDriveConnectionStatusChanged_();
+                }
+              } else if (
+                  volumeMetadata.volumeType ===
+                  VolumeManagerCommon.VolumeType.REMOVABLE) {
+                // Update for remounted USB external storage, because they were
+                // remounted to switch read-only policy.
+                this.volumeInfoList.add(volumeInfo);
+              }
+              return volumeInfo;
+            });
+  }
+
+  /**
+   * Initializes mount points.
+   * @param {function()} callback Called upon the completion of the
+   *     initialization.
+   * @private
+   */
+  initialize_(callback) {
+    chrome.fileManagerPrivate.onMountCompleted.addListener(
+        this.onMountCompleted_.bind(this));
+    console.warn('Requesting volume list.');
+    chrome.fileManagerPrivate.getVolumeMetadataList(volumeMetadataList => {
+      console.warn(
+          'Volume list fetched with: ' + volumeMetadataList.length + ' items.');
+      // We must subscribe to the mount completed event in the callback of
+      // getVolumeMetadataList. crbug.com/330061.
+      // But volumes reported by onMountCompleted events must be added after the
+      // volumes in the volumeMetadataList are mounted. crbug.com/135477.
+      this.mountQueue_.run(inCallback => {
+        // Create VolumeInfo for each volume.
+        Promise
+            .all(volumeMetadataList.map(volumeMetadata => {
+              console.warn('Initializing volume: ' + volumeMetadata.volumeId);
+              return this.addVolumeMetadata_(volumeMetadata)
+                  .then(volumeInfo => {
+                    console.warn('Initialized volume: ' + volumeInfo.volumeId);
+                  });
+            }))
+            .then(() => {
+              console.warn('Initialized all volumes.');
+              // Call the callback of the initialize function.
+              callback();
+              // Call the callback of AsyncQueue. Maybe it invokes callbacks
+              // registered by mountCompleted events.
+              inCallback();
+            });
+      });
+    });
+  }
+
+  /**
+   * Event handler called when some volume was mounted or unmounted.
+   * @param {chrome.fileManagerPrivate.MountCompletedEvent} event Received
+   *     event.
+   * @private
+   */
+  onMountCompleted_(event) {
+    this.mountQueue_.run(callback => {
+      switch (event.eventType) {
+        case 'mount':
+          var requestKey = this.makeRequestKey_(
+              'mount', event.volumeMetadata.sourcePath || '');
+
+          if (event.status === 'success' ||
+              event.status ===
+                  VolumeManagerCommon.VolumeError.UNKNOWN_FILESYSTEM ||
+              event.status ===
+                  VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
+            this.addVolumeMetadata_(event.volumeMetadata).then(volumeInfo => {
+              this.finishRequest_(requestKey, event.status, volumeInfo);
+              callback();
+            });
+          } else if (
+              event.status ===
+              VolumeManagerCommon.VolumeError.ALREADY_MOUNTED) {
+            const navigationEvent =
+                new Event(VolumeManagerCommon.VOLUME_ALREADY_MOUNTED);
+            navigationEvent.volumeId = event.volumeMetadata.volumeId;
+            this.dispatchEvent(navigationEvent);
+            this.finishRequest_(requestKey, event.status);
             callback();
-          });
-        } else if (
-            event.status === VolumeManagerCommon.VolumeError.ALREADY_MOUNTED) {
-          const navigationEvent =
-              new Event(VolumeManagerCommon.VOLUME_ALREADY_MOUNTED);
-          navigationEvent.volumeId = event.volumeMetadata.volumeId;
-          this.dispatchEvent(navigationEvent);
-          this.finishRequest_(requestKey, event.status);
+          } else {
+            console.warn('Failed to mount a volume: ' + event.status);
+            this.finishRequest_(requestKey, event.status);
+            callback();
+          }
+          break;
+
+        case 'unmount':
+          const volumeId = event.volumeMetadata.volumeId;
+          const status = event.status;
+          var requestKey = this.makeRequestKey_('unmount', volumeId);
+          const requested = requestKey in this.requests_;
+          const volumeInfoIndex = this.volumeInfoList.findIndex(volumeId);
+          const volumeInfo = volumeInfoIndex !== -1 ?
+              this.volumeInfoList.item(volumeInfoIndex) :
+              null;
+          if (event.status === 'success' && !requested && volumeInfo) {
+            console.warn('Unmounted volume without a request: ' + volumeId);
+            const e = new Event('externally-unmounted');
+            e.volumeInfo = volumeInfo;
+            this.dispatchEvent(e);
+          }
+
+          this.finishRequest_(requestKey, status);
+          if (event.status === 'success') {
+            this.volumeInfoList.remove(event.volumeMetadata.volumeId);
+          }
+          console.warn('unmounted volume: ' + volumeId);
           callback();
-        } else {
-          console.warn('Failed to mount a volume: ' + event.status);
-          this.finishRequest_(requestKey, event.status);
-          callback();
-        }
-        break;
-
-      case 'unmount':
-        const volumeId = event.volumeMetadata.volumeId;
-        const status = event.status;
-        var requestKey = this.makeRequestKey_('unmount', volumeId);
-        const requested = requestKey in this.requests_;
-        const volumeInfoIndex = this.volumeInfoList.findIndex(volumeId);
-        const volumeInfo = volumeInfoIndex !== -1 ?
-            this.volumeInfoList.item(volumeInfoIndex) :
-            null;
-        if (event.status === 'success' && !requested && volumeInfo) {
-          console.warn('Unmounted volume without a request: ' + volumeId);
-          const e = new Event('externally-unmounted');
-          e.volumeInfo = volumeInfo;
-          this.dispatchEvent(e);
-        }
-
-        this.finishRequest_(requestKey, status);
-        if (event.status === 'success') {
-          this.volumeInfoList.remove(event.volumeMetadata.volumeId);
-        }
-        console.warn('unmounted volume: ' + volumeId);
-        callback();
-        break;
-    }
-  });
-};
-
-/**
- * Creates string to match mount events with requests.
- * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
- *     enum.
- * @param {string} argument Argument describing the request, eg. source file
- *     path of the archive to be mounted, or a volumeId for unmounting.
- * @return {string} Key for |this.requests_|.
- * @private
- */
-VolumeManagerImpl.prototype.makeRequestKey_ = (requestType, argument) => {
-  return requestType + ':' + argument;
-};
-
-/** @override */
-VolumeManagerImpl.prototype.mountArchive = function(
-    fileUrl, successCallback, errorCallback) {
-  chrome.fileManagerPrivate.addMount(fileUrl, sourcePath => {
-    console.info(
-        'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
-    const requestKey = this.makeRequestKey_('mount', sourcePath);
-    this.startRequest_(requestKey, successCallback, errorCallback);
-  });
-};
-
-/** @override */
-VolumeManagerImpl.prototype.unmount = function(
-    volumeInfo, successCallback, errorCallback) {
-  chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
-  const requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
-  this.startRequest_(requestKey, successCallback, errorCallback);
-};
-
-/** @override */
-VolumeManagerImpl.prototype.configure = volumeInfo => {
-  return new Promise((fulfill, reject) => {
-    chrome.fileManagerPrivate.configureVolume(volumeInfo.volumeId, () => {
-      if (chrome.runtime.lastError) {
-        reject(chrome.runtime.lastError.message);
-      } else {
-        fulfill();
+          break;
       }
     });
-  });
-};
+  }
 
-/** @override */
-VolumeManagerImpl.prototype.getVolumeInfo = function(entry) {
-  if (!entry) {
-    console.error('Invalid entry passed to getVolumeInfo: ' + entry);
+  /**
+   * Creates string to match mount events with requests.
+   * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
+   *     enum.
+   * @param {string} argument Argument describing the request, eg. source file
+   *     path of the archive to be mounted, or a volumeId for unmounting.
+   * @return {string} Key for |this.requests_|.
+   * @private
+   */
+  makeRequestKey_(requestType, argument) {
+    return requestType + ':' + argument;
+  }
+
+  /** @override */
+  mountArchive(fileUrl, successCallback, errorCallback) {
+    chrome.fileManagerPrivate.addMount(fileUrl, sourcePath => {
+      console.info(
+          'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
+      const requestKey = this.makeRequestKey_('mount', sourcePath);
+      this.startRequest_(requestKey, successCallback, errorCallback);
+    });
+  }
+
+  /** @override */
+  unmount(volumeInfo, successCallback, errorCallback) {
+    chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
+    const requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
+    this.startRequest_(requestKey, successCallback, errorCallback);
+  }
+
+  /** @override */
+  configure(volumeInfo) {
+    return new Promise((fulfill, reject) => {
+      chrome.fileManagerPrivate.configureVolume(volumeInfo.volumeId, () => {
+        if (chrome.runtime.lastError) {
+          reject(chrome.runtime.lastError.message);
+        } else {
+          fulfill();
+        }
+      });
+    });
+  }
+
+  /** @override */
+  getVolumeInfo(entry) {
+    if (!entry) {
+      console.error('Invalid entry passed to getVolumeInfo: ' + entry);
+      return null;
+    }
+    for (let i = 0; i < this.volumeInfoList.length; i++) {
+      const volumeInfo = this.volumeInfoList.item(i);
+      if (volumeInfo.fileSystem &&
+          util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) {
+        return volumeInfo;
+      }
+      // Additionally, check fake entries.
+      for (let key in volumeInfo.fakeEntries_) {
+        const fakeEntry = volumeInfo.fakeEntries_[key];
+        if (util.isSameEntry(fakeEntry, entry)) {
+          return volumeInfo;
+        }
+      }
+    }
     return null;
   }
-  for (let i = 0; i < this.volumeInfoList.length; i++) {
-    const volumeInfo = this.volumeInfoList.item(i);
-    if (volumeInfo.fileSystem &&
-        util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) {
-      return volumeInfo;
-    }
-    // Additionally, check fake entries.
-    for (let key in volumeInfo.fakeEntries_) {
-      const fakeEntry = volumeInfo.fakeEntries_[key];
-      if (util.isSameEntry(fakeEntry, entry)) {
+
+  /** @override */
+  getCurrentProfileVolumeInfo(volumeType) {
+    for (let i = 0; i < this.volumeInfoList.length; i++) {
+      const volumeInfo = this.volumeInfoList.item(i);
+      if (volumeInfo.profile.isCurrentProfile &&
+          volumeInfo.volumeType === volumeType) {
         return volumeInfo;
       }
     }
+    return null;
   }
-  return null;
-};
 
-/** @override */
-VolumeManagerImpl.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
-  for (let i = 0; i < this.volumeInfoList.length; i++) {
-    const volumeInfo = this.volumeInfoList.item(i);
-    if (volumeInfo.profile.isCurrentProfile &&
-        volumeInfo.volumeType === volumeType) {
-      return volumeInfo;
+  /** @override */
+  getLocationInfo(entry) {
+    if (!entry) {
+      console.error('Invalid entry passed to getLocationInfo: ' + entry);
+      return null;
     }
-  }
-  return null;
-};
+    const volumeInfo = this.getVolumeInfo(entry);
 
-/** @override */
-VolumeManagerImpl.prototype.getLocationInfo = function(entry) {
-  if (!entry) {
-    console.error('Invalid entry passed to getLocationInfo: ' + entry);
-    return null;
-  }
-  const volumeInfo = this.getVolumeInfo(entry);
+    if (util.isFakeEntry(entry)) {
+      return new EntryLocationImpl(
+          volumeInfo, assert(entry.rootType),
+          true /* the entry points a root directory. */,
+          true /* fake entries are read only. */);
+    }
 
-  if (util.isFakeEntry(entry)) {
-    return new EntryLocationImpl(
-        volumeInfo, assert(entry.rootType),
-        true /* the entry points a root directory. */,
-        true /* fake entries are read only. */);
-  }
+    if (!volumeInfo) {
+      return null;
+    }
 
-  if (!volumeInfo) {
-    return null;
-  }
-
-  let rootType;
-  let isReadOnly;
-  let isRootEntry;
-  if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
-    // For Drive, the roots are /root, /team_drives, /Computers and /other,
-    // instead of /. Root URLs contain trailing slashes.
-    if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
-      rootType = VolumeManagerCommon.RootType.DRIVE;
-      isReadOnly = volumeInfo.isReadOnly;
-      isRootEntry = entry.fullPath === '/root';
-    } else if (
-        entry.fullPath == VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH ||
-        entry.fullPath.indexOf(
-            VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH + '/') === 0) {
-      if (entry.fullPath == VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH) {
-        rootType = VolumeManagerCommon.RootType.SHARED_DRIVES_GRAND_ROOT;
-        isReadOnly = true;
-        isRootEntry = true;
-      } else {
-        rootType = VolumeManagerCommon.RootType.SHARED_DRIVE;
-        if (util.isTeamDriveRoot(entry)) {
-          isReadOnly = false;
-          isRootEntry = true;
-        } else {
-          // Regular files/directories under Shared Drives.
-          isRootEntry = false;
-          isReadOnly = volumeInfo.isReadOnly;
-        }
-      }
-    } else if (
-        entry.fullPath == VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH ||
-        entry.fullPath.indexOf(
-            VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH + '/') === 0) {
-      if (entry.fullPath == VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH) {
-        rootType = VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT;
-        isReadOnly = true;
-        isRootEntry = true;
-      } else {
-        rootType = VolumeManagerCommon.RootType.COMPUTER;
-        if (util.isComputersRoot(entry)) {
+    let rootType;
+    let isReadOnly;
+    let isRootEntry;
+    if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.DRIVE) {
+      // For Drive, the roots are /root, /team_drives, /Computers and /other,
+      // instead of /. Root URLs contain trailing slashes.
+      if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
+        rootType = VolumeManagerCommon.RootType.DRIVE;
+        isReadOnly = volumeInfo.isReadOnly;
+        isRootEntry = entry.fullPath === '/root';
+      } else if (
+          entry.fullPath == VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH ||
+          entry.fullPath.indexOf(
+              VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH + '/') === 0) {
+        if (entry.fullPath ==
+            VolumeManagerCommon.SHARED_DRIVES_DIRECTORY_PATH) {
+          rootType = VolumeManagerCommon.RootType.SHARED_DRIVES_GRAND_ROOT;
           isReadOnly = true;
           isRootEntry = true;
         } else {
-          // Regular files/directories under a Computer entry.
-          isRootEntry = false;
-          isReadOnly = volumeInfo.isReadOnly;
+          rootType = VolumeManagerCommon.RootType.SHARED_DRIVE;
+          if (util.isTeamDriveRoot(entry)) {
+            isReadOnly = false;
+            isRootEntry = true;
+          } else {
+            // Regular files/directories under Shared Drives.
+            isRootEntry = false;
+            isReadOnly = volumeInfo.isReadOnly;
+          }
         }
-      }
-    } else if (
-        entry.fullPath == '/other' || entry.fullPath.indexOf('/other/') === 0) {
-      rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
-      isReadOnly = true;
-      isRootEntry = entry.fullPath === '/other';
-    } else if (
-        entry.fullPath === '/.files-by-id' ||
-        entry.fullPath.indexOf('/.files-by-id/') === 0) {
-      rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
+      } else if (
+          entry.fullPath == VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH ||
+          entry.fullPath.indexOf(
+              VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH + '/') === 0) {
+        if (entry.fullPath == VolumeManagerCommon.COMPUTERS_DIRECTORY_PATH) {
+          rootType = VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT;
+          isReadOnly = true;
+          isRootEntry = true;
+        } else {
+          rootType = VolumeManagerCommon.RootType.COMPUTER;
+          if (util.isComputersRoot(entry)) {
+            isReadOnly = true;
+            isRootEntry = true;
+          } else {
+            // Regular files/directories under a Computer entry.
+            isRootEntry = false;
+            isReadOnly = volumeInfo.isReadOnly;
+          }
+        }
+      } else if (
+          entry.fullPath == '/other' ||
+          entry.fullPath.indexOf('/other/') === 0) {
+        rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
+        isReadOnly = true;
+        isRootEntry = entry.fullPath === '/other';
+      } else if (
+          entry.fullPath === '/.files-by-id' ||
+          entry.fullPath.indexOf('/.files-by-id/') === 0) {
+        rootType = VolumeManagerCommon.RootType.DRIVE_OTHER;
 
-      // /.files-by-id/<id> is read-only, but /.files-by-id/<id>/foo is
-      // read-write.
-      isReadOnly = entry.fullPath.split('/').length < 4;
-      isRootEntry = entry.fullPath === '/.files-by-id';
+        // /.files-by-id/<id> is read-only, but /.files-by-id/<id>/foo is
+        // read-write.
+        isReadOnly = entry.fullPath.split('/').length < 4;
+        isRootEntry = entry.fullPath === '/.files-by-id';
+      } else {
+        // Accessing Drive files outside of /drive/root and /drive/other is not
+        // allowed, but can happen. Therefore returning null.
+        return null;
+      }
     } else {
-      // Accessing Drive files outside of /drive/root and /drive/other is not
-      // allowed, but can happen. Therefore returning null.
-      return null;
+      rootType =
+          VolumeManagerCommon.getRootTypeFromVolumeType(volumeInfo.volumeType);
+      isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
+      // Although "Play files" root directory is writable in file system level,
+      // we prohibit write operations on it in the UI level to avoid confusion.
+      // Users can still have write access in sub directories like
+      // /Play files/Pictures, /Play files/DCIM, etc...
+      if (volumeInfo.volumeType ==
+              VolumeManagerCommon.VolumeType.ANDROID_FILES &&
+          isRootEntry) {
+        isReadOnly = true;
+      } else {
+        isReadOnly = volumeInfo.isReadOnly;
+      }
     }
-  } else {
-    rootType =
-        VolumeManagerCommon.getRootTypeFromVolumeType(volumeInfo.volumeType);
-    isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
-    // Although "Play files" root directory is writable in file system level,
-    // we prohibit write operations on it in the UI level to avoid confusion.
-    // Users can still have write access in sub directories like
-    // /Play files/Pictures, /Play files/DCIM, etc...
-    if (volumeInfo.volumeType == VolumeManagerCommon.VolumeType.ANDROID_FILES &&
-        isRootEntry) {
-      isReadOnly = true;
+
+    return new EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly);
+  }
+
+  /** @override */
+  findByDevicePath(devicePath) {
+    for (let i = 0; i < this.volumeInfoList.length; i++) {
+      const volumeInfo = this.volumeInfoList.item(i);
+      if (volumeInfo.devicePath && volumeInfo.devicePath === devicePath) {
+        return volumeInfo;
+      }
+    }
+    return null;
+  }
+
+  /** @override */
+  whenVolumeInfoReady(volumeId) {
+    return new Promise((fulfill) => {
+      const handler = () => {
+        const index = this.volumeInfoList.findIndex(volumeId);
+        if (index !== -1) {
+          fulfill(this.volumeInfoList.item(index));
+          this.volumeInfoList.removeEventListener('splice', handler);
+        }
+      };
+      this.volumeInfoList.addEventListener('splice', handler);
+      handler();
+    });
+  }
+
+  /** @override */
+  getDefaultDisplayRoot(callback) {
+    console.error(
+        'Unexpectedly called VolumeManagerImpl.getDefaultDisplayRoot.');
+    callback(null);
+  }
+
+  /**
+   * @param {string} key Key produced by |makeRequestKey_|.
+   * @param {function(VolumeInfo)} successCallback To be called when the request
+   *     finishes successfully.
+   * @param {function(VolumeManagerCommon.VolumeError)} errorCallback To be
+   *     called when the request fails.
+   * @private
+   */
+  startRequest_(key, successCallback, errorCallback) {
+    if (key in this.requests_) {
+      const request = this.requests_[key];
+      request.successCallbacks.push(successCallback);
+      request.errorCallbacks.push(errorCallback);
     } else {
-      isReadOnly = volumeInfo.isReadOnly;
+      this.requests_[key] = {
+        successCallbacks: [successCallback],
+        errorCallbacks: [errorCallback],
+
+        timeout: setTimeout(
+            this.onTimeout_.bind(this, key), volumeManagerUtil.TIMEOUT)
+      };
     }
   }
 
-  return new EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly);
-};
-
-/** @override */
-VolumeManagerImpl.prototype.findByDevicePath = function(devicePath) {
-  for (let i = 0; i < this.volumeInfoList.length; i++) {
-    const volumeInfo = this.volumeInfoList.item(i);
-    if (volumeInfo.devicePath && volumeInfo.devicePath === devicePath) {
-      return volumeInfo;
-    }
+  /**
+   * Called if no response received in |TIMEOUT|.
+   * @param {string} key Key produced by |makeRequestKey_|.
+   * @private
+   */
+  onTimeout_(key) {
+    this.invokeRequestCallbacks_(
+        this.requests_[key], VolumeManagerCommon.VolumeError.TIMEOUT);
+    delete this.requests_[key];
   }
-  return null;
-};
 
-/** @override */
-VolumeManagerImpl.prototype.whenVolumeInfoReady = function(volumeId) {
-  return new Promise((fulfill) => {
-    const handler = () => {
-      const index = this.volumeInfoList.findIndex(volumeId);
-      if (index !== -1) {
-        fulfill(this.volumeInfoList.item(index));
-        this.volumeInfoList.removeEventListener('splice', handler);
-      }
-    };
-    this.volumeInfoList.addEventListener('splice', handler);
-    handler();
-  });
-};
-
-/** @override */
-VolumeManagerImpl.prototype.getDefaultDisplayRoot = callback => {
-  console.error('Unexpectedly called VolumeManagerImpl.getDefaultDisplayRoot.');
-  callback(null);
-};
-
-/**
- * @param {string} key Key produced by |makeRequestKey_|.
- * @param {function(VolumeInfo)} successCallback To be called when the request
- *     finishes successfully.
- * @param {function(VolumeManagerCommon.VolumeError)} errorCallback To be called
- *     when the request fails.
- * @private
- */
-VolumeManagerImpl.prototype.startRequest_ = function(
-    key, successCallback, errorCallback) {
-  if (key in this.requests_) {
+  /**
+   * @param {string} key Key produced by |makeRequestKey_|.
+   * @param {VolumeManagerCommon.VolumeError|string} status Status received
+   *     from the API.
+   * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
+   * @private
+   */
+  finishRequest_(key, status, opt_volumeInfo) {
     const request = this.requests_[key];
-    request.successCallbacks.push(successCallback);
-    request.errorCallbacks.push(errorCallback);
-  } else {
-    this.requests_[key] = {
-      successCallbacks: [successCallback],
-      errorCallbacks: [errorCallback],
-
-      timeout:
-          setTimeout(this.onTimeout_.bind(this, key), volumeManagerUtil.TIMEOUT)
-    };
-  }
-};
-
-/**
- * Called if no response received in |TIMEOUT|.
- * @param {string} key Key produced by |makeRequestKey_|.
- * @private
- */
-VolumeManagerImpl.prototype.onTimeout_ = function(key) {
-  this.invokeRequestCallbacks_(
-      this.requests_[key], VolumeManagerCommon.VolumeError.TIMEOUT);
-  delete this.requests_[key];
-};
-
-/**
- * @param {string} key Key produced by |makeRequestKey_|.
- * @param {VolumeManagerCommon.VolumeError|string} status Status received
- *     from the API.
- * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
- * @private
- */
-VolumeManagerImpl.prototype.finishRequest_ = function(
-    key, status, opt_volumeInfo) {
-  const request = this.requests_[key];
-  if (!request) {
-    return;
-  }
-
-  clearTimeout(request.timeout);
-  this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
-  delete this.requests_[key];
-};
-
-/**
- * @param {Object} request Structure created in |startRequest_|.
- * @param {VolumeManagerCommon.VolumeError|string} status If status ===
- *     'success' success callbacks are called.
- * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
- * @private
- */
-VolumeManagerImpl.prototype.invokeRequestCallbacks_ = function(
-    request, status, opt_volumeInfo) {
-  const callEach = (callbacks, self, args) => {
-    for (let i = 0; i < callbacks.length; i++) {
-      callbacks[i].apply(self, args);
+    if (!request) {
+      return;
     }
-  };
-  if (status === 'success') {
-    callEach(request.successCallbacks, this, [opt_volumeInfo]);
-  } else {
-    volumeManagerUtil.validateError(status);
-    callEach(request.errorCallbacks, this, [status]);
+
+    clearTimeout(request.timeout);
+    this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
+    delete this.requests_[key];
   }
-};
+
+  /**
+   * @param {Object} request Structure created in |startRequest_|.
+   * @param {VolumeManagerCommon.VolumeError|string} status If status ===
+   *     'success' success callbacks are called.
+   * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
+   * @private
+   */
+  invokeRequestCallbacks_(request, status, opt_volumeInfo) {
+    const callEach = (callbacks, self, args) => {
+      for (let i = 0; i < callbacks.length; i++) {
+        callbacks[i].apply(self, args);
+      }
+    };
+    if (status === 'success') {
+      callEach(request.successCallbacks, this, [opt_volumeInfo]);
+    } else {
+      volumeManagerUtil.validateError(status);
+      callEach(request.errorCallbacks, this, [status]);
+    }
+  }
+}
diff --git a/ui/file_manager/file_manager/cws_widget/app_installer.js b/ui/file_manager/file_manager/cws_widget/app_installer.js
index 1c98aa7..69d31bf0 100644
--- a/ui/file_manager/file_manager/cws_widget/app_installer.js
+++ b/ui/file_manager/file_manager/cws_widget/app_installer.js
@@ -4,18 +4,61 @@
 
 /**
  * Manage the installation of apps.
- *
- * @param {string} itemId Item id to be installed.
- * @param {!CWSWidgetContainerPlatformDelegate} delegate Delegate for accessing
- *     Chrome platform APIs.
- * @constructor
- * @struct
  */
-function AppInstaller(itemId, delegate) {
-  /** @private {!CWSWidgetContainerPlatformDelegate} */
-  this.delegate_ = delegate;
-  this.itemId_ = itemId;
-  this.callback_ = null;
+class AppInstaller {
+  /**
+   * @param {string} itemId Item id to be installed.
+   * @param {!CWSWidgetContainerPlatformDelegate} delegate Delegate for
+   *     accessing Chrome platform APIs.
+   */
+  constructor(itemId, delegate) {
+    /** @private {!CWSWidgetContainerPlatformDelegate} */
+    this.delegate_ = delegate;
+    this.itemId_ = itemId;
+    this.callback_ = null;
+  }
+
+  /**
+   * Start an installation.
+   * @param {function(AppInstaller.Result, string)} callback Called when the
+   *     installation is finished.
+   */
+  install(callback) {
+    this.callback_ = callback;
+    this.delegate_.installWebstoreItem(
+        this.itemId_, this.onInstallCompleted_.bind(this));
+  }
+
+  /**
+   * Prevents {@code this.callback_} from being called.
+   */
+  cancel() {
+    // TODO(tbarzic): Would it make sense to uninstall the app on success if the
+    // app instaler is cancelled instead of just invalidating the callback?
+    this.callback_ = null;
+  }
+
+  /**
+   * Called when the installation is completed.
+   *
+   * @param {?string} error Null if the installation is success,
+   *     otherwise error message.
+   * @private
+   */
+  onInstallCompleted_(error) {
+    if (!this.callback_) {
+      return;
+    }
+
+    var installerResult = AppInstaller.Result.SUCCESS;
+    if (error !== null) {
+      installerResult = error == AppInstaller.USER_CANCELLED_ERROR_STR_ ?
+          AppInstaller.Result.CANCELLED :
+          AppInstaller.Result.ERROR;
+    }
+    this.callback_(installerResult, error || '');
+    this.callback_ = null;
+  }
 }
 
 /**
@@ -39,45 +82,3 @@
  * @private
  */
 AppInstaller.USER_CANCELLED_ERROR_STR_ = 'User cancelled install';
-
-/**
- * Start an installation.
- * @param {function(AppInstaller.Result, string)} callback Called when the
- *     installation is finished.
- */
-AppInstaller.prototype.install = function(callback) {
-  this.callback_ = callback;
-  this.delegate_.installWebstoreItem(
-      this.itemId_, this.onInstallCompleted_.bind(this));
-};
-
-/**
- * Prevents {@code this.callback_} from being called.
- */
-AppInstaller.prototype.cancel = function() {
-  // TODO(tbarzic): Would it make sense to uninstall the app on success if the
-  // app instaler is cancelled instead of just invalidating the callback?
-  this.callback_ = null;
-};
-
-/**
- * Called when the installation is completed.
- *
- * @param {?string} error Null if the installation is success,
- *     otherwise error message.
- * @private
- */
-AppInstaller.prototype.onInstallCompleted_ = function(error) {
-  if (!this.callback_) {
-    return;
-  }
-
-  var installerResult = AppInstaller.Result.SUCCESS;
-  if (error !== null) {
-    installerResult = error == AppInstaller.USER_CANCELLED_ERROR_STR_ ?
-        AppInstaller.Result.CANCELLED :
-        AppInstaller.Result.ERROR;
-  }
-  this.callback_(installerResult, error || '');
-  this.callback_ = null;
-};
diff --git a/ui/file_manager/file_manager/cws_widget/cws_widget_container.js b/ui/file_manager/file_manager/cws_widget/cws_widget_container.js
index c2e590d..fd39e528 100644
--- a/ui/file_manager/file_manager/cws_widget/cws_widget_container.js
+++ b/ui/file_manager/file_manager/cws_widget/cws_widget_container.js
@@ -42,178 +42,632 @@
 
 /**
  * Creates the widget container element in DOM tree.
- *
- * @param {!HTMLDocument} document The document to contain this container.
- * @param {!HTMLElement} parentNode Node to be parent for this container.
- * @param {!CWSWidgetContainerPlatformDelegate} delegate Delegate for accessing
- *     Chrome platform APIs.
- * @param {!{
- *   overrideCwsContainerUrlForTest: (string|undefined),
- *   overrideCwsContainerOriginForTest: (string|undefined)
- * }} params Overrides for container params.
- * @constructor
  */
-function CWSWidgetContainer(document, parentNode, delegate, params) {
-  /** @private {!CWSWidgetContainerPlatformDelegate} */
-  this.delegate_ = delegate;
+class CWSWidgetContainer {
+  /**
+   *
+   * @param {!HTMLDocument} document The document to contain this container.
+   * @param {!HTMLElement} parentNode Node to be parent for this container.
+   * @param {!CWSWidgetContainerPlatformDelegate} delegate Delegate for
+   *     accessing Chrome platform APIs.
+   * @param {!{
+   *   overrideCwsContainerUrlForTest: (string|undefined),
+   *   overrideCwsContainerOriginForTest: (string|undefined)
+   * }} params Overrides for container params.
+   */
+  constructor(document, parentNode, delegate, params) {
+    /** @private {!CWSWidgetContainerPlatformDelegate} */
+    this.delegate_ = delegate;
 
-  /** @private {!CWSWidgetContainer.MetricsRecorder} */
-  this.metricsRecorder_ =
-      new CWSWidgetContainer.MetricsRecorder(delegate.metricsImpl);
+    /** @private {!CWSWidgetContainer.MetricsRecorder} */
+    this.metricsRecorder_ =
+        new CWSWidgetContainer.MetricsRecorder(delegate.metricsImpl);
+
+    /**
+     * The document that will contain the container.
+     * @const {!HTMLDocument}
+     * @private
+     */
+    this.document_ = document;
+
+    /**
+     * The element containing the widget webview.
+     * @type {!Element}
+     * @private
+     */
+    this.webviewContainer_ = document.createElement('div');
+    this.webviewContainer_.classList.add('cws-widget-webview-container');
+    this.webviewContainer_.style.width = WEBVIEW_WIDTH + 'px';
+    this.webviewContainer_.style.height = WEBVIEW_HEIGHT + 'px';
+    parentNode.appendChild(this.webviewContainer_);
+
+    parentNode.classList.add('cws-widget-container-root');
+
+    /**
+     * Element showing spinner layout in place of Web Store widget.
+     * @type {!Element}
+     */
+    var spinnerLayer = document.createElement('div');
+    spinnerLayer.className = 'cws-widget-spinner-layer';
+    parentNode.appendChild(spinnerLayer);
+
+    /** @private {!CWSWidgetContainer.SpinnerLayerController} */
+    this.spinnerLayerController_ =
+        new CWSWidgetContainer.SpinnerLayerController(spinnerLayer);
+
+    /**
+     * The widget container's button strip.
+     * @type {!Element}
+     */
+    var buttons = document.createElement('div');
+    buttons.classList.add('cws-widget-buttons');
+    parentNode.appendChild(buttons);
+
+    /**
+     * Button that opens the Webstore URL.
+     * @const {!Element}
+     * @private
+     */
+    this.webstoreButton_ = document.createElement('div');
+    this.webstoreButton_.hidden = true;
+    this.webstoreButton_.setAttribute('role', 'button');
+    this.webstoreButton_.tabIndex = 0;
+
+    /**
+     * Icon for the Webstore button.
+     * @type {!Element}
+     */
+    var webstoreButtonIcon = this.document_.createElement('span');
+    webstoreButtonIcon.classList.add('cws-widget-webstore-button-icon');
+    this.webstoreButton_.appendChild(webstoreButtonIcon);
+
+    /**
+     * The label for the Webstore button.
+     * @type {!Element}
+     */
+    var webstoreButtonLabel = this.document_.createElement('span');
+    webstoreButtonLabel.classList.add('cws-widget-webstore-button-label');
+    webstoreButtonLabel.textContent = this.delegate_.strings.LINK_TO_WEBSTORE;
+    this.webstoreButton_.appendChild(webstoreButtonLabel);
+
+    this.webstoreButton_.addEventListener(
+        'click', this.onWebstoreLinkActivated_.bind(this));
+    this.webstoreButton_.addEventListener(
+        'keydown', this.onWebstoreLinkKeyDown_.bind(this));
+
+    buttons.appendChild(this.webstoreButton_);
+
+    /**
+     * The webview element containing the Chrome Web Store widget.
+     * @type {?WebView}
+     * @private
+     */
+    this.webview_ = null;
+
+    /**
+     * The Chrome Web Store widget URL.
+     * @const {string}
+     * @private
+     */
+    this.widgetUrl_ = params.overrideCwsContainerUrlForTest || CWS_WIDGET_URL;
+
+    /**
+     * The Chrome Web Store widget origin.
+     * @const {string}
+     * @private
+     */
+    this.widgetOrigin_ =
+        params.overrideCwsContainerOriginForTest || CWS_WIDGET_ORIGIN;
+
+    /**
+     * Map of options for the widget.
+     * @type {?Object<*>}
+     * @private
+     */
+    this.options_ = null;
+
+    /**
+     * The ID of the item being installed. Null if no items are being installed.
+     * @type {?string}
+     * @private
+     */
+    this.installingItemId_ = null;
+
+    /**
+     * The ID of the the installed item. Null if no item was installed.
+     * @type {?string}
+     * @private
+     */
+    this.installedItemId_ = null;
+
+    /**
+     * The current widget state.
+     * @type {CWSWidgetContainer.State}
+     * @private
+     */
+    this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
+
+    /**
+     * The Chrome Web Store access token to be used when communicating with the
+     * Chrome Web Store widget.
+     * @type {?string}
+     * @private
+     */
+    this.accessToken_ = null;
+
+    /**
+     * Called when the Chrome Web Store widget is done. It resolves the promise
+     * returned by {@code this.start()}.
+     * @type {?function(CWSWidgetContainer.ResolveReason)}
+     * @private
+     */
+    this.resolveStart_ = null;
+
+    /**
+     * Promise for retrieving {@code this.accessToken_}.
+     * @type {Promise<string>}
+     * @private
+     */
+    this.tokenGetter_ = this.createTokenGetter_();
+
+    /**
+     * Dialog to be shown when an installation attempt fails.
+     * @type {CWSWidgetContainerErrorDialog}
+     * @private
+     */
+    this.errorDialog_ = new CWSWidgetContainerErrorDialog(parentNode);
+
+    /** @private {?AppInstaller} */
+    this.appInstaller_ = null;
+
+    /** @private {?CWSContainerClient} */
+    this.webviewClient_ = null;
+
+    /** @private {?string} */
+    this.webStoreUrl_ = null;
+  }
 
   /**
-   * The document that will contain the container.
-   * @const {!HTMLDocument}
+   * @return {!Element} The element that should be focused initially.
+   */
+  getInitiallyFocusedElement() {
+    return this.webviewContainer_;
+  }
+
+  /**
+   * Injects headers into the passed request.
+   *
+   * @param {!Object} e Request event.
+   * @return {!BlockingResponse} Modified headers.
    * @private
    */
-  this.document_ = document;
+  authorizeRequest_(e) {
+    e.requestHeaders.push(
+        {name: 'Authorization', value: 'Bearer ' + this.accessToken_});
+    return /** @type {!BlockingResponse}*/ ({requestHeaders: e.requestHeaders});
+  }
 
   /**
-   * The element containing the widget webview.
-   * @type {!Element}
+   * Retrieves the authorize token.
+   * @return {Promise<string>} The promise with the retrieved access token.
    * @private
    */
-  this.webviewContainer_ = document.createElement('div');
-  this.webviewContainer_.classList.add('cws-widget-webview-container');
-  this.webviewContainer_.style.width = WEBVIEW_WIDTH + 'px';
-  this.webviewContainer_.style.height = WEBVIEW_HEIGHT + 'px';
-  parentNode.appendChild(this.webviewContainer_);
+  createTokenGetter_() {
+    return new Promise(function(resolve, reject) {
+      if (window.IN_TEST) {
+        // In test, use a dummy string as token. This must be a non-empty
+        // string.
+        resolve('DUMMY_ACCESS_TOKEN_FOR_TEST');
+        return;
+      }
 
-  parentNode.classList.add('cws-widget-container-root');
+      // Fetch or update the access token.
+      this.delegate_.requestWebstoreAccessToken(
+          /**
+             @param {?string} accessToken The requested token. Null on error.
+               */
+          function(accessToken) {
+            if (!accessToken) {
+              reject('Error retrieving Web Store access token.');
+              return;
+            }
+            resolve(accessToken);
+          });
+    }.bind(this));
+  }
 
   /**
-   * Element showing spinner layout in place of Web Store widget.
-   * @type {!Element}
+   * @return {boolean} Whether the container is in initial state, i.e. inactive.
    */
-  var spinnerLayer = document.createElement('div');
-  spinnerLayer.className = 'cws-widget-spinner-layer';
-  parentNode.appendChild(spinnerLayer);
-
-  /** @private {!CWSWidgetContainer.SpinnerLayerController} */
-  this.spinnerLayerController_ =
-      new CWSWidgetContainer.SpinnerLayerController(spinnerLayer);
+  isInInitialState() {
+    return this.state_ === CWSWidgetContainer.State.UNINITIALIZED;
+  }
 
   /**
-   * The widget container's button strip.
-   * @type {!Element}
+   * Ensures that the widget container is in the state where it can properly
+   * handle showing the Chrome Web Store webview.
+   * @return {Promise} Resolved when the container is ready to be used.
    */
-  var buttons = document.createElement('div');
-  buttons.classList.add('cws-widget-buttons');
-  parentNode.appendChild(buttons);
+  ready() {
+    return new Promise(function(resolve, reject) {
+      if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
+        reject('Invalid state.');
+        return;
+      }
+
+      this.spinnerLayerController_.setAltText(
+          this.delegate_.strings.LOADING_SPINNER_ALT);
+      this.spinnerLayerController_.setVisible(true);
+
+      this.metricsRecorder_.recordShowDialog();
+      this.metricsRecorder_.startLoad();
+
+      this.state_ = CWSWidgetContainer.State.GETTING_ACCESS_TOKEN;
+
+      this.tokenGetter_.then(
+          function(accessToken) {
+            this.state_ = CWSWidgetContainer.State.ACCESS_TOKEN_READY;
+            this.accessToken_ = accessToken;
+            resolve();
+          }.bind(this),
+          function(error) {
+            this.spinnerLayerController_.setVisible(false);
+            this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
+            reject('Failed to get Web Store access token: ' + error);
+          }.bind(this));
+    }.bind(this));
+  }
 
   /**
-   * Button that opens the Webstore URL.
-   * @const {!Element}
+   * Initializes and starts loading the Chrome Web Store widget webview.
+   * Must not be called before {@code this.ready()} is resolved.
+   *
+   * @param {!Object<*>} options Map of options for the dialog.
+   * @param {?string} webStoreUrl Url for more results. Null if not supported.
+   * @return {!Promise<CWSWidgetContainer.ResolveReason>} Resolved when app
+   *     installation is done, or the installation is cancelled.
+   */
+  start(options, webStoreUrl) {
+    return new Promise(function(resolve, reject) {
+      if (this.state_ !== CWSWidgetContainer.State.ACCESS_TOKEN_READY) {
+        this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
+        reject('Invalid state in |start|.');
+        return;
+      }
+
+      if (!this.accessToken_) {
+        this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
+        reject('No access token.');
+        return;
+      }
+
+      this.resolveStart_ = resolve;
+
+      this.state_ = CWSWidgetContainer.State.INITIALIZING;
+
+      this.webStoreUrl_ = webStoreUrl;
+      this.options_ = options;
+
+      this.webstoreButton_.hidden = !webStoreUrl;
+      this.webstoreButton_.classList.toggle(
+          'cws-widget-webstore-button', !!webStoreUrl);
+
+      this.webview_ =
+          /** @type {!WebView} */ (this.document_.createElement('webview'));
+      this.webview_.id = 'cws-widget';
+      this.webview_.partition = 'persist:cwswidgets';
+      this.webview_.style.width = WEBVIEW_WIDTH + 'px';
+      this.webview_.style.height = WEBVIEW_HEIGHT + 'px';
+      this.webview_.request.onBeforeSendHeaders.addListener(
+          this.authorizeRequest_.bind(this),
+          /** @type {!RequestFilter}*/ ({urls: [this.widgetOrigin_ + '/*']}),
+          ['blocking', 'requestHeaders']);
+      this.webview_.addEventListener('newwindow', function(event) {
+        event = /** @type {NewWindowEvent} */ (event);
+        // Discard the window object and reopen in an external window.
+        event.window.discard();
+        window.open(event.targetUrl);
+        event.preventDefault();
+      });
+      this.webviewContainer_.appendChild(this.webview_);
+
+      this.spinnerLayerController_.setElementToFocusOnHide(this.webview_);
+      this.spinnerLayerController_.setAltText(
+          this.delegate_.strings.LOADING_SPINNER_ALT);
+      this.spinnerLayerController_.setVisible(true);
+
+      this.webviewClient_ = new CWSContainerClient(
+          this.webview_, WEBVIEW_WIDTH, WEBVIEW_HEIGHT, this.widgetUrl_,
+          this.widgetOrigin_, this.options_, this.delegate_);
+      this.webviewClient_.addEventListener(
+          CWSContainerClient.Events.LOADED, this.onWidgetLoaded_.bind(this));
+      this.webviewClient_.addEventListener(
+          CWSContainerClient.Events.LOAD_FAILED,
+          this.onWidgetLoadFailed_.bind(this));
+      this.webviewClient_.addEventListener(
+          CWSContainerClient.Events.REQUEST_INSTALL,
+          this.onInstallRequest_.bind(this));
+      this.webviewClient_.addEventListener(
+          CWSContainerClient.Events.INSTALL_DONE,
+          this.onInstallDone_.bind(this));
+      this.webviewClient_.load();
+    }.bind(this));
+  }
+
+  /**
+   * Called when the 'See more...' button is activated. It opens
+   * {@code this.webstoreUrl_}.
+   * @param {Event} e The event that activated the link. Either mouse click or
+   *     key down event.
    * @private
    */
-  this.webstoreButton_ = document.createElement('div');
-  this.webstoreButton_.hidden = true;
-  this.webstoreButton_.setAttribute('role', 'button');
-  this.webstoreButton_.tabIndex = 0;
+  onWebstoreLinkActivated_(e) {
+    if (!this.webStoreUrl_) {
+      return;
+    }
+    window.open(this.webStoreUrl_);
+    this.state_ = CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING;
+    this.reportDone_();
+  }
 
   /**
-   * Icon for the Webstore button.
-   * @type {!Element}
-   */
-  var webstoreButtonIcon = this.document_.createElement('span');
-  webstoreButtonIcon.classList.add('cws-widget-webstore-button-icon');
-  this.webstoreButton_.appendChild(webstoreButtonIcon);
-
-  /**
-   * The label for the Webstore button.
-   * @type {!Element}
-   */
-  var webstoreButtonLabel = this.document_.createElement('span');
-  webstoreButtonLabel.classList.add('cws-widget-webstore-button-label');
-  webstoreButtonLabel.textContent = this.delegate_.strings.LINK_TO_WEBSTORE;
-  this.webstoreButton_.appendChild(webstoreButtonLabel);
-
-  this.webstoreButton_.addEventListener(
-      'click', this.onWebstoreLinkActivated_.bind(this));
-  this.webstoreButton_.addEventListener(
-      'keydown', this.onWebstoreLinkKeyDown_.bind(this));
-
-  buttons.appendChild(this.webstoreButton_);
-
-  /**
-   * The webview element containing the Chrome Web Store widget.
-   * @type {?WebView}
+   * Key down event handler for webstore button element. If the key is enter, it
+   * activates the button.
+   * @param {Event} e The event
    * @private
    */
-  this.webview_ = null;
+  onWebstoreLinkKeyDown_(e) {
+    if (e.keyCode !== 13 /* Enter */) {
+      return;
+    }
+    this.onWebstoreLinkActivated_(e);
+  }
 
   /**
-   * The Chrome Web Store widget URL.
-   * @const {string}
+   * Called when the widget is loaded successfully.
+   * @param {Event} event Event.
    * @private
    */
-  this.widgetUrl_ = params.overrideCwsContainerUrlForTest || CWS_WIDGET_URL;
+  onWidgetLoaded_(event) {
+    this.metricsRecorder_.finishLoad();
+    this.metricsRecorder_.recordLoad(
+        CWSWidgetContainer.MetricsRecorder.LOAD.SUCCEEDED);
+
+    this.state_ = CWSWidgetContainer.State.INITIALIZED;
+
+    this.spinnerLayerController_.setVisible(false);
+    this.webview_.focus();
+  }
 
   /**
-   * The Chrome Web Store widget origin.
-   * @const {string}
+   * Called when the widget is failed to load.
+   * @param {Event} event Event.
    * @private
    */
-  this.widgetOrigin_ =
-      params.overrideCwsContainerOriginForTest || CWS_WIDGET_ORIGIN;
+  onWidgetLoadFailed_(event) {
+    this.metricsRecorder_.recordLoad(
+        CWSWidgetContainer.MetricsRecorder.LOAD.FAILED);
+
+    this.spinnerLayerController_.setVisible(false);
+    this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
+    this.reportDone_();
+  }
 
   /**
-   * Map of options for the widget.
-   * @type {?Object<*>}
-   * @private
+   * Called when the connection status is changed to offline.
    */
-  this.options_ = null;
+  onConnectionLost() {
+    if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
+      this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
+      this.reportDone_();
+    }
+  }
 
   /**
-   * The ID of the item being installed. Null if no items are being installed.
-   * @type {?string}
+   * Called when receiving the install request from the webview client.
+   * @param {Event} e Event.
    * @private
    */
-  this.installingItemId_ = null;
+  onInstallRequest_(e) {
+    var itemId = e.itemId;
+    this.installingItemId_ = itemId;
+
+    this.appInstaller_ = new AppInstaller(itemId, this.delegate_);
+    this.appInstaller_.install(this.onItemInstalled_.bind(this));
+
+    this.spinnerLayerController_.setAltText(
+        this.delegate_.strings.INSTALLING_SPINNER_ALT);
+    this.spinnerLayerController_.setVisible(true);
+    this.state_ = CWSWidgetContainer.State.INSTALLING;
+  }
 
   /**
-   * The ID of the the installed item. Null if no item was installed.
-   * @type {?string}
+   * Called when the webview client receives install confirmation from the
+   * Web Store widget.
+   * @param {Event} e Event
    * @private
    */
-  this.installedItemId_ = null;
+  onInstallDone_(e) {
+    this.spinnerLayerController_.setVisible(false);
+    this.state_ = CWSWidgetContainer.State.INSTALLED_CLOSING;
+    this.reportDone_();
+  }
 
   /**
-   * The current widget state.
-   * @type {CWSWidgetContainer.State}
+   * Called when the installation is completed from the app installer.
+   * @param {AppInstaller.Result} result Result of the installation.
+   * @param {string} error Detail of the error.
    * @private
    */
-  this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
+  onItemInstalled_(result, error) {
+    var success = (result === AppInstaller.Result.SUCCESS);
+
+    // If install succeeded, the spinner will be removed once
+    // |this.webviewClient_| dispatched INSTALL_DONE event.
+    if (!success) {
+      this.spinnerLayerController_.setVisible(false);
+    }
+
+    this.state_ = success ?
+        CWSWidgetContainer.State.WAITING_FOR_CONFIRMATION :
+        CWSWidgetContainer.State.INITIALIZED;  // Back to normal state.
+    this.webviewClient_.onInstallCompleted(
+        success, assert(this.installingItemId_));
+    this.installedItemId_ = this.installingItemId_;
+    this.installingItemId_ = null;
+
+    switch (result) {
+      case AppInstaller.Result.SUCCESS:
+        this.metricsRecorder_.recordInstall(
+            CWSWidgetContainer.MetricsRecorder.INSTALL.SUCCEEDED);
+        // Wait for the widget webview container to dispatch INSTALL_DONE.
+        break;
+      case AppInstaller.Result.CANCELLED:
+        this.metricsRecorder_.recordInstall(
+            CWSWidgetContainer.MetricsRecorder.INSTALL.CANCELLED);
+        // User cancelled the installation. Do nothing.
+        break;
+      case AppInstaller.Result.ERROR:
+        this.metricsRecorder_.recordInstall(
+            CWSWidgetContainer.MetricsRecorder.INSTALL.FAILED);
+        this.errorDialog_.show(
+            this.delegate_.strings.INSTALLATION_FAILED_MESSAGE, null, null,
+            null);
+        break;
+    }
+  }
 
   /**
-   * The Chrome Web Store access token to be used when communicating with the
-   * Chrome Web Store widget.
-   * @type {?string}
+   * Resolves the promise returned by {@code this.start} when widget is done
+   * with installing apps.
    * @private
    */
-  this.accessToken_ = null;
+  reportDone_() {
+    if (this.resolveStart_) {
+      this.resolveStart_(CWSWidgetContainer.ResolveReason.DONE);
+    }
+    this.resolveStart_ = null;
+  }
 
   /**
-   * Called when the Chrome Web Store widget is done. It resolves the promise
-   * returned by {@code this.start()}.
-   * @type {?function(CWSWidgetContainer.ResolveReason)}
-   * @private
+   * Finalizes the widget container state and returns the final app installation
+   * result. The widget should not be used after calling this. If called before
+   * promise returned by {@code this.start} is resolved, the reported result
+   * will be as if the widget was cancelled.
+   * @return {{result: CWSWidgetContainer.Result, installedItemId: ?string}}
    */
-  this.resolveStart_ = null;
+  finalizeAndGetResult() {
+    switch (this.state_) {
+      case CWSWidgetContainer.State.INSTALLING:
+        // Install is being aborted. Send the failure result.
+        // Cancels the install.
+        if (this.webviewClient_) {
+          this.webviewClient_.onInstallCompleted(
+              false, assert(this.installingItemId_));
+        }
+        this.installingItemId_ = null;
+
+        // Assumes closing the dialog as canceling the install.
+        this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
+        break;
+      case CWSWidgetContainer.State.GETTING_ACCESS_TOKEN:
+      case CWSWidgetContainer.State.ACCESS_TOKEN_READY:
+      case CWSWidgetContainer.State.INITIALIZING:
+        this.metricsRecorder_.recordLoad(
+            CWSWidgetContainer.MetricsRecorder.LOAD.CANCELLED);
+        this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
+        break;
+      case CWSWidgetContainer.State.WAITING_FOR_CONFIRMATION:
+        // This can happen if the dialog is closed by the user before Web Store
+        // widget replies with 'after_install'.
+        // Consider this success, as the app has actually been installed.
+        // TODO(tbarzic): Should the app be uninstalled in this case?
+        this.state_ = CWSWidgetContainer.State.INSTALLED_CLOSING;
+        break;
+      case CWSWidgetContainer.State.INSTALLED_CLOSING:
+      case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING:
+      case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING:
+        // Do nothing.
+        break;
+      case CWSWidgetContainer.State.INITIALIZED:
+        this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
+        break;
+      default:
+        this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
+        console.error('Invalid state.');
+    }
+
+    var result;
+    switch (this.state_) {
+      case CWSWidgetContainer.State.INSTALLED_CLOSING:
+        result = CWSWidgetContainer.Result.INSTALL_SUCCESSFUL;
+        this.metricsRecorder_.recordCloseDialog(
+            CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.ITEM_INSTALLED);
+        break;
+      case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING:
+        result = CWSWidgetContainer.Result.FAILED;
+        break;
+      case CWSWidgetContainer.State.CANCELED_CLOSING:
+        result = CWSWidgetContainer.Result.USER_CANCEL;
+        this.metricsRecorder_.recordCloseDialog(
+            CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.USER_CANCELLED);
+        break;
+      case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING:
+        result = CWSWidgetContainer.Result.WEBSTORE_LINK_OPENED;
+        this.metricsRecorder_.recordCloseDialog(
+            CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG
+                .WEBSTORE_LINK_OPENED);
+        break;
+      default:
+        result = CWSWidgetContainer.Result.USER_CANCEL;
+        this.metricsRecorder_.recordCloseDialog(
+            CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.UNKNOWN_ERROR);
+    }
+
+    this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
+
+    this.reset_();
+
+    return {result: result, installedItemId: this.installedItemId_};
+  }
 
   /**
-   * Promise for retrieving {@code this.accessToken_}.
-   * @type {Promise<string>}
+   * Resets the widget.
    * @private
    */
-  this.tokenGetter_ = this.createTokenGetter_();
+  reset_() {
+    if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
+      console.error('Widget reset before its state was finalized.');
+    }
 
-  /**
-   * Dialog to be shown when an installation attempt fails.
-   * @type {CWSWidgetContainerErrorDialog}
-   * @private
-   */
-  this.errorDialog_ = new CWSWidgetContainerErrorDialog(parentNode);
+    if (this.resolveStart_) {
+      this.resolveStart_(CWSWidgetContainer.ResolveReason.RESET);
+      this.resolveStart_ = null;
+    }
+
+    this.spinnerLayerController_.reset();
+
+    if (this.webviewClient_) {
+      this.webviewClient_.dispose();
+      this.webviewClient_ = null;
+    }
+
+    if (this.webview_) {
+      this.webviewContainer_.removeChild(this.webview_);
+      this.webview_ = null;
+    }
+
+    if (this.appInstaller_) {
+      this.appInstaller_.cancel();
+      this.appInstaller_ = null;
+    }
+
+    this.options_ = null;
+
+    if (this.errorDialog_.shown()) {
+      this.errorDialog_.hide();
+    }
+  }
 }
 
 /**
@@ -265,595 +719,206 @@
 };
 Object.freeze(CWSWidgetContainer.ResolveReason);
 
-/**
- * @return {!Element} The element that should be focused initially.
- */
-CWSWidgetContainer.prototype.getInitiallyFocusedElement = function() {
-  return this.webviewContainer_;
-};
 
-/**
- * Injects headers into the passed request.
- *
- * @param {!Object} e Request event.
- * @return {!BlockingResponse} Modified headers.
- * @private
- */
-CWSWidgetContainer.prototype.authorizeRequest_ = function(e) {
-  e.requestHeaders.push(
-      {name: 'Authorization', value: 'Bearer ' + this.accessToken_});
-  return /** @type {!BlockingResponse}*/ ({requestHeaders: e.requestHeaders});
-};
-
-/**
- * Retrieves the authorize token.
- * @return {Promise<string>} The promise with the retrieved access token.
- * @private
- */
-CWSWidgetContainer.prototype.createTokenGetter_ = function() {
-  return new Promise(function(resolve, reject) {
-    if (window.IN_TEST) {
-      // In test, use a dummy string as token. This must be a non-empty string.
-      resolve('DUMMY_ACCESS_TOKEN_FOR_TEST');
-      return;
-    }
-
-    // Fetch or update the access token.
-    this.delegate_.requestWebstoreAccessToken(
-        /** @param {?string} accessToken The requested token. Null on error. */
-        function(accessToken) {
-          if (!accessToken) {
-            reject('Error retrieving Web Store access token.');
-            return;
-          }
-          resolve(accessToken);
-        });
-  }.bind(this));
-};
-
-/**
- * @return {boolean} Whether the container is in initial state, i.e. inactive.
- */
-CWSWidgetContainer.prototype.isInInitialState = function() {
-  return this.state_ === CWSWidgetContainer.State.UNINITIALIZED;
-};
-
-/**
- * Ensures that the widget container is in the state where it can properly
- * handle showing the Chrome Web Store webview.
- * @return {Promise} Resolved when the container is ready to be used.
- */
-CWSWidgetContainer.prototype.ready = function() {
-  return new Promise(function(resolve, reject) {
-    if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
-      reject('Invalid state.');
-      return;
-    }
-
-    this.spinnerLayerController_.setAltText(
-        this.delegate_.strings.LOADING_SPINNER_ALT);
-    this.spinnerLayerController_.setVisible(true);
-
-    this.metricsRecorder_.recordShowDialog();
-    this.metricsRecorder_.startLoad();
-
-    this.state_ = CWSWidgetContainer.State.GETTING_ACCESS_TOKEN;
-
-    this.tokenGetter_.then(
-        function(accessToken) {
-          this.state_ = CWSWidgetContainer.State.ACCESS_TOKEN_READY;
-          this.accessToken_ = accessToken;
-          resolve();
-        }.bind(this),
-        function(error) {
-          this.spinnerLayerController_.setVisible(false);
-          this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
-          reject('Failed to get Web Store access token: ' + error);
-        }.bind(this));
-  }.bind(this));
-};
-
-/**
- * Initializes and starts loading the Chrome Web Store widget webview.
- * Must not be called before {@code this.ready()} is resolved.
- *
- * @param {!Object<*>} options Map of options for the dialog.
- * @param {?string} webStoreUrl Url for more results. Null if not supported.
- * @return {!Promise<CWSWidgetContainer.ResolveReason>} Resolved when app
- *     installation is done, or the installation is cancelled.
- */
-CWSWidgetContainer.prototype.start = function(options, webStoreUrl) {
-  return new Promise(function(resolve, reject) {
-    if (this.state_ !== CWSWidgetContainer.State.ACCESS_TOKEN_READY) {
-      this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
-      reject('Invalid state in |start|.');
-      return;
-    }
-
-    if (!this.accessToken_) {
-      this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
-      reject('No access token.');
-      return;
-    }
-
-    this.resolveStart_ = resolve;
-
-    this.state_ = CWSWidgetContainer.State.INITIALIZING;
-
-    this.webStoreUrl_ = webStoreUrl;
-    this.options_ = options;
-
-    this.webstoreButton_.hidden = !webStoreUrl;
-    this.webstoreButton_.classList.toggle(
-        'cws-widget-webstore-button', !!webStoreUrl);
-
-    this.webview_ =
-        /** @type {!WebView} */ (this.document_.createElement('webview'));
-    this.webview_.id = 'cws-widget';
-    this.webview_.partition = 'persist:cwswidgets';
-    this.webview_.style.width = WEBVIEW_WIDTH + 'px';
-    this.webview_.style.height = WEBVIEW_HEIGHT + 'px';
-    this.webview_.request.onBeforeSendHeaders.addListener(
-        this.authorizeRequest_.bind(this),
-        /** @type {!RequestFilter}*/ ({urls: [this.widgetOrigin_ + '/*']}),
-        ['blocking', 'requestHeaders']);
-    this.webview_.addEventListener('newwindow', function(event) {
-      event = /** @type {NewWindowEvent} */ (event);
-      // Discard the window object and reopen in an external window.
-      event.window.discard();
-      window.open(event.targetUrl);
-      event.preventDefault();
-    });
-    this.webviewContainer_.appendChild(this.webview_);
-
-    this.spinnerLayerController_.setElementToFocusOnHide(this.webview_);
-    this.spinnerLayerController_.setAltText(
-        this.delegate_.strings.LOADING_SPINNER_ALT);
-    this.spinnerLayerController_.setVisible(true);
-
-    this.webviewClient_ = new CWSContainerClient(
-        this.webview_, WEBVIEW_WIDTH, WEBVIEW_HEIGHT, this.widgetUrl_,
-        this.widgetOrigin_, this.options_, this.delegate_);
-    this.webviewClient_.addEventListener(
-        CWSContainerClient.Events.LOADED, this.onWidgetLoaded_.bind(this));
-    this.webviewClient_.addEventListener(
-        CWSContainerClient.Events.LOAD_FAILED,
-        this.onWidgetLoadFailed_.bind(this));
-    this.webviewClient_.addEventListener(
-        CWSContainerClient.Events.REQUEST_INSTALL,
-        this.onInstallRequest_.bind(this));
-    this.webviewClient_.addEventListener(
-        CWSContainerClient.Events.INSTALL_DONE, this.onInstallDone_.bind(this));
-    this.webviewClient_.load();
-  }.bind(this));
-};
-
-/**
- * Called when the 'See more...' button is activated. It opens
- * {@code this.webstoreUrl_}.
- * @param {Event} e The event that activated the link. Either mouse click or
- *     key down event.
- * @private
- */
-CWSWidgetContainer.prototype.onWebstoreLinkActivated_ = function(e) {
-  if (!this.webStoreUrl_) {
-    return;
-  }
-  window.open(this.webStoreUrl_);
-  this.state_ = CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING;
-  this.reportDone_();
-};
-
-/**
- * Key down event handler for webstore button element. If the key is enter, it
- * activates the button.
- * @param {Event} e The event
- * @private
- */
-CWSWidgetContainer.prototype.onWebstoreLinkKeyDown_ = function(e) {
-  if (e.keyCode !== 13 /* Enter */) {
-    return;
-  }
-  this.onWebstoreLinkActivated_(e);
-};
-
-/**
- * Called when the widget is loaded successfully.
- * @param {Event} event Event.
- * @private
- */
-CWSWidgetContainer.prototype.onWidgetLoaded_ = function(event) {
-  this.metricsRecorder_.finishLoad();
-  this.metricsRecorder_.recordLoad(
-      CWSWidgetContainer.MetricsRecorder.LOAD.SUCCEEDED);
-
-  this.state_ = CWSWidgetContainer.State.INITIALIZED;
-
-  this.spinnerLayerController_.setVisible(false);
-  this.webview_.focus();
-};
-
-/**
- * Called when the widget is failed to load.
- * @param {Event} event Event.
- * @private
- */
-CWSWidgetContainer.prototype.onWidgetLoadFailed_ = function(event) {
-  this.metricsRecorder_.recordLoad(
-      CWSWidgetContainer.MetricsRecorder.LOAD.FAILED);
-
-  this.spinnerLayerController_.setVisible(false);
-  this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
-  this.reportDone_();
-};
-
-/**
- * Called when the connection status is changed to offline.
- */
-CWSWidgetContainer.prototype.onConnectionLost = function() {
-  if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
-    this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING;
-    this.reportDone_();
-  }
-};
-
-/**
- * Called when receiving the install request from the webview client.
- * @param {Event} e Event.
- * @private
- */
-CWSWidgetContainer.prototype.onInstallRequest_ = function(e) {
-  var itemId = e.itemId;
-  this.installingItemId_ = itemId;
-
-  this.appInstaller_ = new AppInstaller(itemId, this.delegate_);
-  this.appInstaller_.install(this.onItemInstalled_.bind(this));
-
-  this.spinnerLayerController_.setAltText(
-      this.delegate_.strings.INSTALLING_SPINNER_ALT);
-  this.spinnerLayerController_.setVisible(true);
-  this.state_ = CWSWidgetContainer.State.INSTALLING;
-};
-
-/**
- * Called when the webview client receives install confirmation from the
- * Web Store widget.
- * @param {Event} e Event
- * @private
- */
-CWSWidgetContainer.prototype.onInstallDone_ = function(e) {
-  this.spinnerLayerController_.setVisible(false);
-  this.state_ = CWSWidgetContainer.State.INSTALLED_CLOSING;
-  this.reportDone_();
-};
-
-/**
- * Called when the installation is completed from the app installer.
- * @param {AppInstaller.Result} result Result of the installation.
- * @param {string} error Detail of the error.
- * @private
- */
-CWSWidgetContainer.prototype.onItemInstalled_ = function(result, error) {
-  var success = (result === AppInstaller.Result.SUCCESS);
-
-  // If install succeeded, the spinner will be removed once
-  // |this.webviewClient_| dispatched INSTALL_DONE event.
-  if (!success) {
-    this.spinnerLayerController_.setVisible(false);
-  }
-
-  this.state_ = success ?
-      CWSWidgetContainer.State.WAITING_FOR_CONFIRMATION :
-      CWSWidgetContainer.State.INITIALIZED;  // Back to normal state.
-  this.webviewClient_.onInstallCompleted(success, this.installingItemId_);
-  this.installedItemId_ = this.installingItemId_;
-  this.installingItemId_ = null;
-
-  switch (result) {
-    case AppInstaller.Result.SUCCESS:
-      this.metricsRecorder_.recordInstall(
-          CWSWidgetContainer.MetricsRecorder.INSTALL.SUCCEEDED);
-      // Wait for the widget webview container to dispatch INSTALL_DONE.
-      break;
-    case AppInstaller.Result.CANCELLED:
-      this.metricsRecorder_.recordInstall(
-          CWSWidgetContainer.MetricsRecorder.INSTALL.CANCELLED);
-      // User cancelled the installation. Do nothing.
-      break;
-    case AppInstaller.Result.ERROR:
-      this.metricsRecorder_.recordInstall(
-          CWSWidgetContainer.MetricsRecorder.INSTALL.FAILED);
-      this.errorDialog_.show(
-          this.delegate_.strings.INSTALLATION_FAILED_MESSAGE, null, null, null);
-      break;
-  }
-};
-
-/**
- * Resolves the promise returned by {@code this.start} when widget is done with
- * installing apps.
- * @private
- */
-CWSWidgetContainer.prototype.reportDone_ = function() {
-  if (this.resolveStart_) {
-    this.resolveStart_(CWSWidgetContainer.ResolveReason.DONE);
-  }
-  this.resolveStart_ = null;
-};
-
-/**
- * Finalizes the widget container state and returns the final app installation
- * result. The widget should not be used after calling this. If called before
- * promise returned by {@code this.start} is resolved, the reported result will
- * be as if the widget was cancelled.
- * @return {{result: CWSWidgetContainer.Result, installedItemId: ?string}}
- */
-CWSWidgetContainer.prototype.finalizeAndGetResult = function() {
-  switch (this.state_) {
-    case CWSWidgetContainer.State.INSTALLING:
-      // Install is being aborted. Send the failure result.
-      // Cancels the install.
-      if (this.webviewClient_) {
-        this.webviewClient_.onInstallCompleted(false, this.installingItemId_);
-      }
-      this.installingItemId_ = null;
-
-      // Assumes closing the dialog as canceling the install.
-      this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
-      break;
-    case CWSWidgetContainer.State.GETTING_ACCESS_TOKEN:
-    case CWSWidgetContainer.State.ACCESS_TOKEN_READY:
-    case CWSWidgetContainer.State.INITIALIZING:
-      this.metricsRecorder_.recordLoad(
-          CWSWidgetContainer.MetricsRecorder.LOAD.CANCELLED);
-      this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
-      break;
-    case CWSWidgetContainer.State.WAITING_FOR_CONFIRMATION:
-      // This can happen if the dialog is closed by the user before Web Store
-      // widget replies with 'after_install'.
-      // Consider this success, as the app has actually been installed.
-      // TODO(tbarzic): Should the app be uninstalled in this case?
-      this.state_ = CWSWidgetContainer.State.INSTALLED_CLOSING;
-      break;
-    case CWSWidgetContainer.State.INSTALLED_CLOSING:
-    case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING:
-    case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING:
-      // Do nothing.
-      break;
-    case CWSWidgetContainer.State.INITIALIZED:
-      this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
-      break;
-    default:
-      this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING;
-      console.error('Invalid state.');
-  }
-
-  var result;
-  switch (this.state_) {
-    case CWSWidgetContainer.State.INSTALLED_CLOSING:
-      result = CWSWidgetContainer.Result.INSTALL_SUCCESSFUL;
-      this.metricsRecorder_.recordCloseDialog(
-          CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.ITEM_INSTALLED);
-      break;
-    case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING:
-      result = CWSWidgetContainer.Result.FAILED;
-      break;
-    case CWSWidgetContainer.State.CANCELED_CLOSING:
-      result = CWSWidgetContainer.Result.USER_CANCEL;
-      this.metricsRecorder_.recordCloseDialog(
-          CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.USER_CANCELLED);
-      break;
-    case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING:
-      result = CWSWidgetContainer.Result.WEBSTORE_LINK_OPENED;
-      this.metricsRecorder_.recordCloseDialog(
-          CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.WEBSTORE_LINK_OPENED);
-      break;
-    default:
-      result = CWSWidgetContainer.Result.USER_CANCEL;
-      this.metricsRecorder_.recordCloseDialog(
-          CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.UNKNOWN_ERROR);
-  }
-
-  this.state_ = CWSWidgetContainer.State.UNINITIALIZED;
-
-  this.reset_();
-
-  return {result: result, installedItemId: this.installedItemId_};
-};
-
-/**
- * Resets the widget.
- * @private
- */
-CWSWidgetContainer.prototype.reset_ = function() {
-  if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) {
-    console.error('Widget reset before its state was finalized.');
-  }
-
-  if (this.resolveStart_) {
-    this.resolveStart_(CWSWidgetContainer.ResolveReason.RESET);
-    this.resolveStart_ = null;
-  }
-
-  this.spinnerLayerController_.reset();
-
-  if (this.webviewClient_) {
-    this.webviewClient_.dispose();
-    this.webviewClient_ = null;
-  }
-
-  if (this.webview_) {
-    this.webviewContainer_.removeChild(this.webview_);
-    this.webview_ = null;
-  }
-
-  if (this.appInstaller_) {
-    this.appInstaller_.cancel();
-    this.appInstaller_ = null;
-  }
-
-  this.options_ = null;
-
-  if (this.errorDialog_.shown()) {
-    this.errorDialog_.hide();
-  }
-};
-
-/**
- * Controls showing and hiding spinner layer.
- * @param {!Element} spinnerLayer The spinner layer element.
- * @constructor
- */
-CWSWidgetContainer.SpinnerLayerController = function(spinnerLayer) {
-  /** @private {!Element} */
-  this.spinnerLayer_ = spinnerLayer;
-
-  /** @private {boolean} */
-  this.visible_ = false;
-
+CWSWidgetContainer.SpinnerLayerController = class {
   /**
-   * Set only if spinner is transitioning between visible and hidden states.
-   * Calling the function clears event handlers set for handling the transition,
-   * and updates spinner layer class list to its final state.
-   * @type {?function()}
-   * @private
+   * Controls showing and hiding spinner layer.
+   * @param {!Element} spinnerLayer The spinner layer element.
    */
-  this.clearTransition_ = null;
+  constructor(spinnerLayer) {
+    /** @private {!Element} */
+    this.spinnerLayer_ = spinnerLayer;
 
-  /**
-   * Reference to the timeout set to ensure {@code this.clearTransision_} gets
-   * called even if 'transitionend' event does not fire.
-   * @type {?number}
-   * @private
-   */
-  this.clearTransitionTimeout_ = null;
+    /** @private {boolean} */
+    this.visible_ = false;
 
-  /**
-   * Element to be focused when the layer is hidden.
-   * @type {Element}
-   * @private
-   */
-  this.focusOnHide_ = null;
-
-  spinnerLayer.tabIndex = -1;
-
-  // Prevent default Tab key handling in order to prevent the widget from
-  // taking the focus while the spinner layer is active.
-  // NOTE: This assumes that there are no elements allowed to become active
-  // while the spinner is shown. Something smarter would be needed if this
-  // assumption becomes invalid.
-  spinnerLayer.addEventListener('keydown', this.handleKeyDown_.bind(this));
-};
-
-/**
- * Sets element to be focused when the layer is hidden.
- * @param {!Element} el
- */
-CWSWidgetContainer.SpinnerLayerController.prototype.setElementToFocusOnHide =
-    function(el) {
-  this.focusOnHide_ = el;
-};
-
-/**
- * Prevents default Tab key handling in order to prevent spinner layer from
- * losing focus.
- * @param {Event} e The key down event.
- * @private
- */
-CWSWidgetContainer.SpinnerLayerController.prototype.handleKeyDown_ = function(
-    e) {
-  if (!this.visible_) {
-    return;
-  }
-  if (e.keyCode === 9 /* Tab */) {
-    e.preventDefault();
-  }
-};
-
-/**
- * Resets the spinner layer controllers state, and makes sure the spinner
- * layre gets hidden.
- */
-CWSWidgetContainer.SpinnerLayerController.prototype.reset = function() {
-  this.visible_ = false;
-  this.focusOnHide_ = null;
-  if (this.clearTransision_) {
-    this.clearTransition_();
-  }
-};
-
-/**
- * Sets alt text for the spinner layer.
- * @param {string} text
- */
-CWSWidgetContainer.SpinnerLayerController.prototype.setAltText = function(
-    text) {
-  this.spinnerLayer_.setAttribute('aria-label', text);
-};
-
-/**
- * Shows or hides the spinner layer and handles the layer's opacity transition.
- * @param {boolean} visible Whether the layer should become visible.
- */
-CWSWidgetContainer.SpinnerLayerController.prototype.setVisible = function(
-    visible) {
-  if (this.visible_ === visible) {
-    return;
-  }
-
-  if (this.clearTransition_) {
-    this.clearTransition_();
-  }
-
-  this.visible_ = visible;
-
-  // Spinner should be shown during transition.
-  this.spinnerLayer_.classList.toggle('cws-widget-show-spinner', true);
-
-  if (this.visible_) {
-    this.spinnerLayer_.focus();
-  } else if (this.focusOnHide_) {
-    this.focusOnHide_.focus();
-  }
-
-  if (!this.visible_) {
-    this.spinnerLayer_.classList.add('cws-widget-hiding-spinner');
-  }
-
-  this.clearTransition_ = function() {
-    if (this.clearTransitionTimeout_) {
-      clearTimeout(this.clearTransitionTimeout_);
-    }
-    this.clearTransitionTimeout_ = null;
-
-    this.spinnerLayer_.removeEventListener(
-        'transitionend', this.clearTransition_);
+    /**
+     * Set only if spinner is transitioning between visible and hidden states.
+     * Calling the function clears event handlers set for handling the
+     * transition, and updates spinner layer class list to its final state.
+     * @type {?function()}
+     * @private
+     */
     this.clearTransition_ = null;
 
-    if (!this.visible_) {
-      this.spinnerLayer_.classList.remove('cws-widget-hiding-spinner');
-      this.spinnerLayer_.classList.remove('cws-widget-show-spinner');
-    }
-  }.bind(this);
-
-  this.spinnerLayer_.addEventListener('transitionend', this.clearTransition_);
-
-  // Ensure the transition state gets cleared, even if transitionend is not
-  // fired.
-  this.clearTransitionTimeout_ = setTimeout(function() {
+    /**
+     * Reference to the timeout set to ensure {@code this.clearTransition_} gets
+     * called even if 'transitionend' event does not fire.
+     * @type {?number}
+     * @private
+     */
     this.clearTransitionTimeout_ = null;
-    this.clearTransition_();
-  }.bind(this), 550 /* ms */);
+
+    /**
+     * Element to be focused when the layer is hidden.
+     * @type {Element}
+     * @private
+     */
+    this.focusOnHide_ = null;
+
+    spinnerLayer.tabIndex = -1;
+
+    // Prevent default Tab key handling in order to prevent the widget from
+    // taking the focus while the spinner layer is active.
+    // NOTE: This assumes that there are no elements allowed to become active
+    // while the spinner is shown. Something smarter would be needed if this
+    // assumption becomes invalid.
+    spinnerLayer.addEventListener('keydown', this.handleKeyDown_.bind(this));
+  }
+
+  /**
+   * Sets element to be focused when the layer is hidden.
+   * @param {!Element} el
+   */
+  setElementToFocusOnHide(el) {
+    this.focusOnHide_ = el;
+  }
+
+  /**
+   * Prevents default Tab key handling in order to prevent spinner layer from
+   * losing focus.
+   * @param {Event} e The key down event.
+   * @private
+   */
+  handleKeyDown_(e) {
+    if (!this.visible_) {
+      return;
+    }
+    if (e.keyCode === 9 /* Tab */) {
+      e.preventDefault();
+    }
+  }
+
+  /**
+   * Resets the spinner layer controllers state, and makes sure the spinner
+   * layre gets hidden.
+   */
+  reset() {
+    this.visible_ = false;
+    this.focusOnHide_ = null;
+    if (this.clearTransition_) {
+      this.clearTransition_();
+    }
+  }
+
+  /**
+   * Sets alt text for the spinner layer.
+   * @param {string} text
+   */
+  setAltText(text) {
+    this.spinnerLayer_.setAttribute('aria-label', text);
+  }
+
+  /**
+   * Shows or hides the spinner layer and handles the layer's opacity
+   * transition.
+   * @param {boolean} visible Whether the layer should become visible.
+   */
+  setVisible(visible) {
+    if (this.visible_ === visible) {
+      return;
+    }
+
+    if (this.clearTransition_) {
+      this.clearTransition_();
+    }
+
+    this.visible_ = visible;
+
+    // Spinner should be shown during transition.
+    this.spinnerLayer_.classList.toggle('cws-widget-show-spinner', true);
+
+    if (this.visible_) {
+      this.spinnerLayer_.focus();
+    } else if (this.focusOnHide_) {
+      this.focusOnHide_.focus();
+    }
+
+    if (!this.visible_) {
+      this.spinnerLayer_.classList.add('cws-widget-hiding-spinner');
+    }
+
+    this.clearTransition_ = function() {
+      if (this.clearTransitionTimeout_) {
+        clearTimeout(this.clearTransitionTimeout_);
+      }
+      this.clearTransitionTimeout_ = null;
+
+      this.spinnerLayer_.removeEventListener(
+          'transitionend', this.clearTransition_);
+      this.clearTransition_ = null;
+
+      if (!this.visible_) {
+        this.spinnerLayer_.classList.remove('cws-widget-hiding-spinner');
+        this.spinnerLayer_.classList.remove('cws-widget-show-spinner');
+      }
+    }.bind(this);
+
+    this.spinnerLayer_.addEventListener('transitionend', this.clearTransition_);
+
+    // Ensure the transition state gets cleared, even if transitionend is not
+    // fired.
+    this.clearTransitionTimeout_ = setTimeout(function() {
+      this.clearTransitionTimeout_ = null;
+      this.clearTransition_();
+    }.bind(this), 550 /* ms */);
+  }
 };
 
 /**
  * Utility methods and constants to record histograms.
- * @param {!CWSWidgetContainerMetricsImpl} metricsImpl
- * @constructor
  */
-CWSWidgetContainer.MetricsRecorder = function(metricsImpl) {
-  /** @private {!CWSWidgetContainerMetricsImpl} */
-  this.metricsImpl_ = metricsImpl;
+CWSWidgetContainer.MetricsRecorder = class {
+  /**
+   * @param {!CWSWidgetContainerMetricsImpl} metricsImpl
+   */
+  constructor(metricsImpl) {
+    /** @private {!CWSWidgetContainerMetricsImpl} */
+    this.metricsImpl_ = metricsImpl;
+  }
+
+
+  /**
+   * @param {number} result Result of load, which must be defined in
+   *     CWSWidgetContainer.MetricsRecorder.LOAD.
+   */
+  recordLoad(result) {
+    if (0 <= result && result < 3) {
+      this.metricsImpl_.recordEnum('Load', result, 3);
+    }
+  }
+
+  /**
+   * @param {number} reason Reason of closing dialog, which must be defined in
+   *     CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.
+   */
+  recordCloseDialog(reason) {
+    if (0 <= reason && reason < 4) {
+      this.metricsImpl_.recordEnum('CloseDialog', reason, 4);
+    }
+  }
+
+  /**
+   * @param {number} result Result of installation, which must be defined in
+   *     CWSWidgetContainer.MetricsRecorder.INSTALL.
+   */
+  recordInstall(result) {
+    if (0 <= result && result < 3) {
+      this.metricsImpl_.recordEnum('Install', result, 3);
+    }
+  }
+
+  recordShowDialog() {
+    this.metricsImpl_.recordUserAction('ShowDialog');
+  }
+
+  startLoad() {
+    this.metricsImpl_.startInterval('LoadTime');
+  }
+
+  finishLoad() {
+    this.metricsImpl_.recordInterval('LoadTime');
+  }
 };
 
 /**
@@ -886,46 +951,3 @@
   CANCELLED: 1,
   FAILED: 2,
 };
-
-/**
- * @param {number} result Result of load, which must be defined in
- *     CWSWidgetContainer.MetricsRecorder.LOAD.
- */
-CWSWidgetContainer.MetricsRecorder.prototype.recordLoad = function(result) {
-  if (0 <= result && result < 3) {
-    this.metricsImpl_.recordEnum('Load', result, 3);
-  }
-};
-
-/**
- * @param {number} reason Reason of closing dialog, which must be defined in
- *     CWSWidgetContainer.MetricsRecorder.CLOSE_DIALOG.
- */
-CWSWidgetContainer.MetricsRecorder.prototype.recordCloseDialog = function(
-    reason) {
-  if (0 <= reason && reason < 4) {
-    this.metricsImpl_.recordEnum('CloseDialog', reason, 4);
-  }
-};
-
-/**
- * @param {number} result Result of installation, which must be defined in
- *     CWSWidgetContainer.MetricsRecorder.INSTALL.
- */
-CWSWidgetContainer.MetricsRecorder.prototype.recordInstall = function(result) {
-  if (0 <= result && result < 3) {
-    this.metricsImpl_.recordEnum('Install', result, 3);
-  }
-};
-
-CWSWidgetContainer.MetricsRecorder.prototype.recordShowDialog = function() {
-  this.metricsImpl_.recordUserAction('ShowDialog');
-};
-
-CWSWidgetContainer.MetricsRecorder.prototype.startLoad = function() {
-  this.metricsImpl_.startInterval('LoadTime');
-};
-
-CWSWidgetContainer.MetricsRecorder.prototype.finishLoad = function() {
-  this.metricsImpl_.recordInterval('LoadTime');
-};
diff --git a/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js b/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js
index 538b008..153a068 100644
--- a/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/actions_model_unittest.js
@@ -24,31 +24,38 @@
  */
 let driveSyncHandler;
 
+
 /**
- * MockFolderShortcutsModel
- * @extends {FolderShortcutsDataModel}
- * @constructor
+ * @returns {!FolderShortcutsDataModel}
  */
-function MockFolderShortcutsModel() {
-  this.has = false;
+function createFakeFolderShortcutsDataModel() {
+  class FakeFolderShortcutsModel extends cr.EventTarget {
+    constructor() {
+      super();
+      this.has = false;
+    }
+
+    exists() {
+      return this.has;
+    }
+
+    add(entry) {
+      this.has = true;
+      return 0;
+    }
+
+    remove(entry) {
+      this.has = false;
+      return 0;
+    }
+  }
+
+  const model = /** @type {!Object} */ (new FakeFolderShortcutsModel());
+  return /** @type {!FolderShortcutsDataModel} */ (model);
 }
 
-MockFolderShortcutsModel.prototype.exists = function() {
-  return this.has;
-};
-
-MockFolderShortcutsModel.prototype.add = function(entry) {
-  this.has = true;
-  return 0;
-};
-
-MockFolderShortcutsModel.prototype.remove = function(entry) {
-  this.has = false;
-  return 0;
-};
-
 /**
- * @type {!MockFolderShortcutsModel}
+ * @type {!FolderShortcutsDataModel}
  */
 let shortcutsModel;
 
@@ -111,7 +118,7 @@
       assert(volumeManager.getCurrentProfileVolumeInfo(type).fileSystem);
 
   // Create mock action model components.
-  shortcutsModel = new MockFolderShortcutsModel();
+  shortcutsModel = createFakeFolderShortcutsDataModel();
   driveSyncHandler = new MockDriveSyncHandler();
   ui = new MockUI();
 }
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index e635015..100b106 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -122,34 +122,10 @@
 };
 
 /**
- * Obtains an entry from the give navigation model item.
- * @param {!NavigationModelItem} item Navigation model item.
- * @return {Entry} Related entry.
- * @private
- */
-CommandUtil.getEntryFromNavigationModelItem_ = item => {
-  switch (item.type) {
-    case NavigationModelItemType.VOLUME:
-      return /** @type {!NavigationModelVolumeItem} */ (item)
-          .volumeInfo.displayRoot;
-    case NavigationModelItemType.SHORTCUT:
-      return /** @type {!NavigationModelShortcutItem} */ (item).entry;
-  }
-  return null;
-};
-
-/**
- * Checks if command can be executed on drive.
- * @param {!Event} event Command event to mark.
- * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
- */
-CommandUtil.canExecuteEnabledOnDriveOnly = (event, fileManager) => {
-  event.canExecute = fileManager.directoryModel.isOnDrive();
-};
-
-/**
  * Sets the command as visible only when the current volume is drive and it's
  * running as a normal app, not as a modal dialog.
+ * NOTE: This doesn't work for directory tree menu, because user can right-click
+ * on any visible volume.
  * @param {!Event} event Command event to mark.
  * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
  */
diff --git a/ui/file_manager/file_manager/foreground/js/folder_shortcuts_data_model.js b/ui/file_manager/file_manager/foreground/js/folder_shortcuts_data_model.js
index eed06c0f..25e258e 100644
--- a/ui/file_manager/file_manager/foreground/js/folder_shortcuts_data_model.js
+++ b/ui/file_manager/file_manager/foreground/js/folder_shortcuts_data_model.js
@@ -12,66 +12,58 @@
  * Model for the folder shortcuts. This object is cr.ui.ArrayDataModel-like
  * object with additional methods for the folder shortcut feature.
  * This uses chrome.storage as backend. Items are always sorted by URL.
- *
- * @param {!FilteredVolumeManager} volumeManager Volume manager instance.
- * @constructor
- * @extends {cr.EventTarget}
  */
-function FolderShortcutsDataModel(volumeManager) {
-  this.volumeManager_ = volumeManager;
-  this.array_ = [];
-  this.pendingPaths_ = {};  // Hash map for easier deleting.
-  this.unresolvablePaths_ = {};
-  this.lastDriveRootURL_ = null;
+class FolderShortcutsDataModel extends cr.EventTarget {
+  /**
+   * @param {!FilteredVolumeManager} volumeManager Volume manager instance.
+   */
+  constructor(volumeManager) {
+    super();
 
-  // Queue to serialize resolving entries.
-  this.queue_ = new AsyncUtil.Queue();
-  this.queue_.run(
-      this.volumeManager_.ensureInitialized.bind(this.volumeManager_));
+    this.volumeManager_ = volumeManager;
+    this.array_ = [];
+    this.pendingPaths_ = {};  // Hash map for easier deleting.
+    this.unresolvablePaths_ = {};
+    this.lastDriveRootURL_ = null;
 
-  // Load the shortcuts. Runs within the queue.
-  this.load_();
+    // Queue to serialize resolving entries.
+    this.queue_ = new AsyncUtil.Queue();
+    this.queue_.run(
+        this.volumeManager_.ensureInitialized.bind(this.volumeManager_));
 
-  // Listening for changes in the storage.
-  chrome.storage.onChanged.addListener((changes, namespace) => {
-    if (!(FolderShortcutsDataModel.NAME in changes) || namespace !== 'sync') {
-      return;
-    }
-    this.reload_();  // Runs within the queue.
-  });
+    // Load the shortcuts. Runs within the queue.
+    this.load_();
 
-  // If the volume info list is changed, then shortcuts have to be reloaded.
-  this.volumeManager_.volumeInfoList.addEventListener(
-      'permuted', this.reload_.bind(this));
+    // Listening for changes in the storage.
+    chrome.storage.onChanged.addListener((changes, namespace) => {
+      if (!(FolderShortcutsDataModel.NAME in changes) || namespace !== 'sync') {
+        return;
+      }
+      this.reload_();  // Runs within the queue.
+    });
 
-  // If the drive status has changed, then shortcuts have to be re-resolved.
-  this.volumeManager_.addEventListener(
-      'drive-connection-changed', this.reload_.bind(this));
-}
+    // If the volume info list is changed, then shortcuts have to be reloaded.
+    this.volumeManager_.volumeInfoList.addEventListener(
+        'permuted', this.reload_.bind(this));
 
-/**
- * Key name in chrome.storage. The array are stored with this name.
- * @type {string}
- * @const
- */
-FolderShortcutsDataModel.NAME = 'folder-shortcuts-list';
-
-FolderShortcutsDataModel.prototype = {
-  __proto__: cr.EventTarget.prototype,
+    // If the drive status has changed, then shortcuts have to be re-resolved.
+    this.volumeManager_.addEventListener(
+        'drive-connection-changed', this.reload_.bind(this));
+  }
 
   /**
    * @return {number} Number of elements in the array.
    */
   get length() {
     return this.array_.length;
-  },
+  }
 
   /**
    * Remembers the Drive volume's root URL used for conversions between virtual
    * paths and URLs.
    * @private
    */
-  rememberLastDriveURL_: function() {
+  rememberLastDriveURL_() {
     if (this.lastDriveRootURL_) {
       return;
     }
@@ -80,14 +72,14 @@
     if (volumeInfo) {
       this.lastDriveRootURL_ = volumeInfo.fileSystem.root.toURL();
     }
-  },
+  }
 
   /**
    * Resolves Entries from a list of stored virtual paths. Runs within a queue.
    * @param {Array<string>} list List of virtual paths.
    * @private
    */
-  processEntries_: function(list) {
+  processEntries_(list) {
     this.queue_.run(callback => {
       this.pendingPaths_ = {};
       this.unresolvablePaths_ = {};
@@ -188,13 +180,13 @@
         queueCallback();
       });
     });
-  },
+  }
 
   /**
    * Initializes the model and loads the shortcuts.
    * @private
    */
-  load_: function() {
+  load_() {
     this.queue_.run(callback => {
       chrome.storage.sync.get(FolderShortcutsDataModel.NAME, value => {
         if (chrome.runtime.lastError) {
@@ -214,13 +206,13 @@
         callback();
       });
     });
-  },
+  }
 
   /**
    * Reloads the model and loads the shortcuts.
    * @private
    */
-  reload_: function() {
+  reload_() {
     let shortcutPaths;
     this.queue_.run(callback => {
       chrome.storage.sync.get(FolderShortcutsDataModel.NAME, value => {
@@ -229,7 +221,7 @@
         callback();
       });
     });
-  },
+  }
 
   /**
    * Returns the entries in the given range as a new array instance. The
@@ -239,24 +231,24 @@
    * @param {number=} opt_end Where to end the selection.
    * @return {Array<Entry>} Entries in the selected range.
    */
-  slice: function(begin, opt_end) {
+  slice(begin, opt_end) {
     return this.array_.slice(begin, opt_end);
-  },
+  }
 
   /**
    * @param {number} index Index of the element to be retrieved.
    * @return {Entry} The value of the |index|-th element.
    */
-  item: function(index) {
+  item(index) {
     return this.array_[index];
-  },
+  }
 
   /**
    * @param {string} value URL of the entry to be found.
    * @return {number} Index of the element with the specified |value|.
    * @private
    */
-  getIndexByURL_: function(value) {
+  getIndexByURL_(value) {
     for (let i = 0; i < this.length; i++) {
       // Same item check: must be exact match.
       if (this.array_[i].toURL() === value) {
@@ -264,13 +256,13 @@
       }
     }
     return -1;
-  },
+  }
 
   /**
    * @param {Entry} value Value of the element to be retrieved.
    * @return {number} Index of the element with the specified |value|.
    */
-  getIndex: function(value) {
+  getIndex(value) {
     for (let i = 0; i < this.length; i++) {
       // Same item check: must be exact match.
       if (util.isSameEntry(this.array_[i], value)) {
@@ -278,7 +270,7 @@
       }
     }
     return -1;
-  },
+  }
 
   /**
    * Compares 2 entries and returns a number indicating one entry comes before
@@ -289,9 +281,9 @@
    * @return {number} Returns -1, if |a| < |b|. Returns 0, if |a| === |b|.
    *     Otherwise, returns 1.
    */
-  compare: function(a, b) {
+  compare(a, b) {
     return util.comparePath(a, b);
-  },
+  }
 
   /**
    * Adds the given item to the array. If there were already same item in the
@@ -301,12 +293,12 @@
    * @param {Entry} value Value to be added into the array.
    * @return {number} Index in the list which the element added to.
    */
-  add: function(value) {
+  add(value) {
     const result = this.addInternal_(value);
     metrics.recordUserAction('FolderShortcut.Add');
     this.save_();
     return result;
-  },
+  }
 
   /**
    * Adds the given item to the array. If there were already same item in the
@@ -317,7 +309,7 @@
    * @return {number} Index in the list which the element added to.
    * @private
    */
-  addInternal_: function(value) {
+  addInternal_(value) {
     this.rememberLastDriveURL_();  // Required for saving.
 
     const oldArray = this.array_.slice(0);  // Shallow copy.
@@ -344,21 +336,21 @@
 
     this.firePermutedEvent_(this.calculatePermutation_(oldArray, this.array_));
     return addedIndex;
-  },
+  }
 
   /**
    * Removes the given item from the array.
    * @param {Entry} value Value to be removed from the array.
    * @return {number} Index in the list which the element removed from.
    */
-  remove: function(value) {
+  remove(value) {
     const result = this.removeInternal_(value);
     if (result !== -1) {
       this.save_();
       metrics.recordUserAction('FolderShortcut.Remove');
     }
     return result;
-  },
+  }
 
   /**
    * Removes the given item from the array.
@@ -367,7 +359,7 @@
    * @return {number} Index in the list which the element removed from.
    * @private
    */
-  removeInternal_: function(value) {
+  removeInternal_(value) {
     let removedIndex = -1;
     const oldArray = this.array_.slice(0);  // Shallow copy.
     for (let i = 0; i < this.length; i++) {
@@ -387,23 +379,23 @@
 
     // No item is removed.
     return -1;
-  },
+  }
 
   /**
    * @param {Entry} entry Entry to be checked.
    * @return {boolean} True if the given |entry| exists in the array. False
    *     otherwise.
    */
-  exists: function(entry) {
+  exists(entry) {
     const index = this.getIndex(entry);
     return (index >= 0);
-  },
+  }
 
   /**
    * Saves the current array to chrome.storage.
    * @private
    */
-  save_: function() {
+  save_() {
     this.rememberLastDriveURL_();
     if (!this.lastDriveRootURL_) {
       return;
@@ -421,7 +413,7 @@
     const prefs = {};
     prefs[FolderShortcutsDataModel.NAME] = paths;
     chrome.storage.sync.set(prefs, () => {});
-  },
+  }
 
   /**
    * Creates a permutation array for 'permuted' event, which is compatible with
@@ -432,7 +424,7 @@
    * @return {Array<number>} Created permutation array.
    * @private
    */
-  calculatePermutation_: function(oldArray, newArray) {
+  calculatePermutation_(oldArray, newArray) {
     let oldIndex = 0;  // Index of oldArray.
     let newIndex = 0;  // Index of newArray.
 
@@ -466,13 +458,13 @@
       }
     }
     return permutation;
-  },
+  }
 
   /**
    * Fires a 'permuted' event, which is compatible with cr.ui.ArrayDataModel.
    * @param {Array<number>} permutation Permutation array.
    */
-  firePermutedEvent_: function(permutation) {
+  firePermutedEvent_(permutation) {
     const permutedEvent = new Event('permuted');
     permutedEvent.newLength = this.length;
     permutedEvent.permutation = permutation;
@@ -484,13 +476,13 @@
     // 2) 'splice' and 'sorted' events are not implemented. These events are
     //    not used in NavigationListModel. We have to implement them when
     //    necessary.
-  },
+  }
 
   /**
    * Called externally when one of the items is not found on the filesystem.
    * @param {Entry} entry The entry which is not found.
    */
-  onItemNotFoundError: function(entry) {
+  onItemNotFoundError(entry) {
     // If Drive is online, then delete the shortcut permanently. Otherwise,
     // delete from model and add to |unresolvablePaths_|.
     if (this.volumeManager_.getDriveConnectionState().type !==
@@ -501,7 +493,7 @@
     }
     this.removeInternal_(entry);
     this.save_();
-  },
+  }
 
   /**
    * Converts the given "stored path" to the URL.
@@ -514,14 +506,14 @@
    * @return {?string} URL of the given path.
    * @private
    */
-  convertStoredPathToUrl_: function(path) {
+  convertStoredPathToUrl_(path) {
     if (path.indexOf(STORED_DRIVE_MOUNT_PATH + '/') !== 0) {
       console.warn(path + ' is neither a drive mount path nor a stored path.');
       return null;
     }
     return this.lastDriveRootURL_ +
         encodeURIComponent(path.substr(STORED_DRIVE_MOUNT_PATH.length));
-  },
+  }
 
   /**
    * Converts the URL to the stored-formatted path.
@@ -532,7 +524,7 @@
    * @return {?string} Path with the stored drive mount path.
    * @private
    */
-  convertUrlToStoredPath_: function(url) {
+  convertUrlToStoredPath_(url) {
     // Root URLs contain a trailing slash.
     if (url.indexOf(this.lastDriveRootURL_) !== 0) {
       console.warn(url + ' is not a drive URL.');
@@ -541,5 +533,12 @@
 
     return STORED_DRIVE_MOUNT_PATH + '/' +
         decodeURIComponent(url.substr(this.lastDriveRootURL_.length));
-  },
-};
+  }
+}
+
+/**
+ * Key name in chrome.storage. The array are stored with this name.
+ * @type {string}
+ * @const
+ */
+FolderShortcutsDataModel.NAME = 'folder-shortcuts-list';
diff --git a/ui/file_manager/image_loader/piex/Makefile b/ui/file_manager/image_loader/piex/Makefile
index 0c6e6639..7e264c0a 100644
--- a/ui/file_manager/image_loader/piex/Makefile
+++ b/ui/file_manager/image_loader/piex/Makefile
@@ -19,7 +19,8 @@
   -s ENVIRONMENT='web' \
   -s NO_DYNAMIC_EXECUTION=1 \
   -s EXTRA_EXPORTED_RUNTIME_METHODS='$(WAPI)' \
-  -s RESERVED_FUNCTION_POINTERS=2
+  -s RESERVED_FUNCTION_POINTERS=$(shell echo $$((10*2))) \
+  -s TOTAL_STACK=$(shell echo $$((8*1024)))
 
 all: a.out.js
 	$(shell cp a.out.* extension)
diff --git a/ui/file_manager/image_loader/piex/piex.cpp b/ui/file_manager/image_loader/piex/piex.cpp
index 33c26ba..a6bc668 100644
--- a/ui/file_manager/image_loader/piex/piex.cpp
+++ b/ui/file_manager/image_loader/piex/piex.cpp
@@ -143,11 +143,3 @@
 }
 
 }  // extern "C"
-
-int main(int argc, char* argv[]) {
-  // clang-format off
-  return EM_ASM_INT({
-    console.log('[piexwasm] main');
-  });
-  // clang-format on
-}
diff --git a/ui/file_manager/image_loader/piex/tests.html b/ui/file_manager/image_loader/piex/tests.html
index f9cd1c35..975791c 100644
--- a/ui/file_manager/image_loader/piex/tests.html
+++ b/ui/file_manager/image_loader/piex/tests.html
@@ -133,6 +133,28 @@
     });
   }
 
+  function readFromFileSystem(image) {
+    return new Promise((resolve, reject) => {
+      document.title = image;
+
+      function failure(error) {
+        reject(new Error('Reading file system: ' + error.name));
+      }
+
+      function readEntry(fileEntry) {
+        fileEntry.file((file) => {
+          const reader = new FileReader();
+          reader.onerror = failure;
+          reader.onload = () => resolve(reader.result);
+          reader.readAsArrayBuffer(file);
+        }, failure);
+      }
+
+      window.fileSystem.root.getFile(
+          image.replace('images/', ''), {}, readEntry, failure);
+    });
+  }
+
   function hashUint8Array(data, hash = ~0) {
     for (let i = 0; i < data.byteLength; ++i)
       hash = (hash << 5) - hash + data[i];
diff --git a/ui/file_manager/image_loader/piex/tests.js b/ui/file_manager/image_loader/piex/tests.js
index 8c28f0f2..fa1ff13d 100644
--- a/ui/file_manager/image_loader/piex/tests.js
+++ b/ui/file_manager/image_loader/piex/tests.js
@@ -67,10 +67,8 @@
     console.log(message.text());
   });
 
-  const url = 'http://localhost:8123/' + process.argv[2];
-  await page.goto(url, {waitUntil: 'networkidle2'}).catch((error) => {
-    console.log(error.message, url);
-    process.exit(1);
+  await page.goto('http://localhost:8123/' + process.argv[2], {
+    waitUntil: 'networkidle2'
   });
 
   await page.mainFrame().waitForFunction('document.title == "READY"');
diff --git a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
index 4f0cae7ec..f0038b3 100644
--- a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
+++ b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
@@ -647,6 +647,12 @@
    * Tests context menu for a Zip root and a folder inside it.
    */
   testcase.dirContextMenuZip = async () => {
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.zipArchive.targetPath],
+      openType: 'launch'
+    });
+
     const zipMenus = [
       ['#unmount', true],
     ];
diff --git a/ui/file_manager/integration_tests/file_manager/file_dialog.js b/ui/file_manager/integration_tests/file_manager/file_dialog.js
index 118308c..3569249e 100644
--- a/ui/file_manager/integration_tests/file_manager/file_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/file_dialog.js
@@ -84,6 +84,11 @@
 async function openFileDialogClickOkButton(
     volume, name, useBrowserOpen = false) {
   const okButton = '.button-panel button.ok:enabled';
+  if (volume !== 'drive' ||
+      await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+    await sendTestMessage(
+        {name: 'expectFileTask', fileNames: [name], openType: 'open'});
+  }
   let closer = clickOpenFileDialogButton.bind(null, name, okButton);
 
   const entrySet = await setUpFileEntrySet(volume);
@@ -109,6 +114,12 @@
 async function saveFileDialogClickOkButton(volume, name) {
   const caller = getCaller();
 
+  if (volume !== 'drive' ||
+      await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+    await sendTestMessage(
+        {name: 'expectFileTask', fileNames: [name], openType: 'saveAs'});
+  }
+
   let closer = async (appId) => {
     const okButton = '.button-panel button.ok:enabled';
 
diff --git a/ui/file_manager/integration_tests/file_manager/launcher_search.js b/ui/file_manager/integration_tests/file_manager/launcher_search.js
index d3f740e3..7addd7a 100644
--- a/ui/file_manager/integration_tests/file_manager/launcher_search.js
+++ b/ui/file_manager/integration_tests/file_manager/launcher_search.js
@@ -14,6 +14,12 @@
   testcase.launcherOpenSearchResult = async () => {
     const imageName = ENTRIES.desktop.nameText;
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.desktop.targetPath],
+      openType: 'launch'
+    });
+
     // Create an image file in Drive.
     await addEntries(['drive'], [ENTRIES.desktop]);
 
diff --git a/ui/file_manager/integration_tests/file_manager/open_audio_files.js b/ui/file_manager/integration_tests/file_manager/open_audio_files.js
index e03a67d..61dab88 100644
--- a/ui/file_manager/integration_tests/file_manager/open_audio_files.js
+++ b/ui/file_manager/integration_tests/file_manager/open_audio_files.js
@@ -90,6 +90,12 @@
   async function audioOpenClose(path) {
     const track = [ENTRIES.beautiful];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add an audio file to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, track, track);
 
@@ -117,6 +123,12 @@
   async function audioOpenTrackDownloads() {
     const track = [ENTRIES.beautiful];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on Downloads, add an audio file to Downloads.
     const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, track, []);
 
@@ -147,6 +159,16 @@
   async function audioOpenMultipleTracksDrive() {
     const tracks = [ENTRIES.beautiful, ENTRIES.newlyAdded];
 
+    // File open events are not reported for legacy Drive.
+    if (await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+      await sendTestMessage({
+        name: 'expectFileTask',
+        fileNames:
+            [ENTRIES.beautiful.targetPath, ENTRIES.newlyAdded.targetPath],
+        openType: 'launch'
+      });
+    }
+
     // Open Files.App on Drive, add the audio files to Drive.
     const appId = await setupAndWaitUntilReady(RootPath.DRIVE, [], tracks);
 
@@ -203,6 +225,12 @@
   async function audioAutoAdvance(path) {
     const tracks = [ENTRIES.beautiful, ENTRIES.newlyAdded];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add audio files to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, tracks, tracks);
 
@@ -236,6 +264,12 @@
   async function audioRepeatAllModeSingleFile(path) {
     const track = [ENTRIES.beautiful];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add an audio file to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, track, track);
 
@@ -277,6 +311,12 @@
   async function audioNoRepeatModeSingleFile(path) {
     const track = [ENTRIES.beautiful];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add an audio file to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, track, track);
 
@@ -311,6 +351,12 @@
   async function audioRepeatOneModeSingleFile(path) {
     const track = [ENTRIES.beautiful];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.beautiful.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add an audio file to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, track, track);
 
@@ -359,6 +405,12 @@
   async function audioRepeatAllModeMultipleFile(path) {
     const tracks = [ENTRIES.beautiful, ENTRIES.newlyAdded];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.newlyAdded.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add audio files to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, tracks, tracks);
 
@@ -406,6 +458,12 @@
   async function audioNoRepeatModeMultipleFile(path) {
     const tracks = [ENTRIES.beautiful, ENTRIES.newlyAdded];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.newlyAdded.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add audio files to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, tracks, tracks);
 
@@ -440,6 +498,12 @@
   async function audioRepeatOneModeMultipleFile(path) {
     const tracks = [ENTRIES.beautiful, ENTRIES.newlyAdded];
 
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.newlyAdded.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add audio files to Downloads and Drive.
     const appId = await setupAndWaitUntilReady(path, tracks, tracks);
 
diff --git a/ui/file_manager/integration_tests/file_manager/open_image_files.js b/ui/file_manager/integration_tests/file_manager/open_image_files.js
index 88e9b34..1844223 100644
--- a/ui/file_manager/integration_tests/file_manager/open_image_files.js
+++ b/ui/file_manager/integration_tests/file_manager/open_image_files.js
@@ -11,6 +11,16 @@
    * @param {string} path Directory path (Downloads or Drive).
    */
   async function imageOpen(path) {
+    // File open events are not reported for legacy Drive.
+    if (path !== RootPath.DRIVE ||
+        await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+      await sendTestMessage({
+        name: 'expectFileTask',
+        fileNames: [ENTRIES.image3.targetPath],
+        openType: 'launch'
+      });
+    }
+
     // Open Files.App on |path|, add image3 to Downloads and Drive.
     const appId =
         await setupAndWaitUntilReady(path, [ENTRIES.image3], [ENTRIES.image3]);
@@ -38,6 +48,16 @@
    * @param {string} path Directory path (Downloads or Drive).
    */
   async function imageOpenGalleryOpen(path) {
+    // File open events are not reported for legacy Drive.
+    if (path !== RootPath.DRIVE ||
+        await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+      await sendTestMessage({
+        name: 'expectFileTask',
+        fileNames: [ENTRIES.image3.targetPath, ENTRIES.desktop.targetPath],
+        openType: 'launch'
+      });
+    }
+
     const testImages = [ENTRIES.image3, ENTRIES.desktop];
 
     // Open Files.App on |path|, add test images to Downloads and Drive.
diff --git a/ui/file_manager/integration_tests/file_manager/open_sniffed_files.js b/ui/file_manager/integration_tests/file_manager/open_sniffed_files.js
index 9fe814a..e4595797 100644
--- a/ui/file_manager/integration_tests/file_manager/open_sniffed_files.js
+++ b/ui/file_manager/integration_tests/file_manager/open_sniffed_files.js
@@ -14,6 +14,12 @@
    * @param {string} path Directory path (Downloads or Drive).
    */
   async function pdfOpen(path) {
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.imgPdf.targetPath],
+      openType: 'launch'
+    });
+
     // Open Files.App on |path|, add imgpdf to Downloads and Drive.
     const appId =
         await setupAndWaitUntilReady(path, [ENTRIES.imgPdf], [ENTRIES.imgPdf]);
diff --git a/ui/file_manager/integration_tests/file_manager/open_video_files.js b/ui/file_manager/integration_tests/file_manager/open_video_files.js
index 09a4f10c..d0f5d508 100644
--- a/ui/file_manager/integration_tests/file_manager/open_video_files.js
+++ b/ui/file_manager/integration_tests/file_manager/open_video_files.js
@@ -28,6 +28,16 @@
    * @param {string} path Directory path to be tested.
    */
   async function videoOpen(path) {
+    // File open events are not reported for legacy Drive.
+    if (path !== RootPath.DRIVE ||
+        await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+      await sendTestMessage({
+        name: 'expectFileTask',
+        fileNames: ['world.ogv'],
+        openType: 'launch'
+      });
+    }
+
     const appId = await setupAndWaitUntilReady(path);
 
     // Open the video.
diff --git a/ui/file_manager/integration_tests/file_manager/suggest_app_dialog.js b/ui/file_manager/integration_tests/file_manager/suggest_app_dialog.js
index 88ada719..254c5969 100644
--- a/ui/file_manager/integration_tests/file_manager/suggest_app_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/suggest_app_dialog.js
@@ -8,6 +8,12 @@
  * Tests sharing a file on Drive
  */
 testcase.suggestAppDialog = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: ['unsupported.foo'],
+    openType: 'launch'
+  });
+
   // Fetch the mock CWS page data.
   const data =
       JSON.parse(await sendTestMessage({name: 'getCwsWidgetContainerMockUrl'}));
diff --git a/ui/file_manager/integration_tests/file_manager/zip_files.js b/ui/file_manager/integration_tests/file_manager/zip_files.js
index 6bdf50f..f5e9222 100644
--- a/ui/file_manager/integration_tests/file_manager/zip_files.js
+++ b/ui/file_manager/integration_tests/file_manager/zip_files.js
@@ -89,6 +89,12 @@
  * Tests zip file open (aka unzip) from Downloads.
  */
 testcase.zipFileOpenDownloads = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchive.targetPath],
+    openType: 'launch'
+  });
+
   // Open Files app on Downloads containing a zip file.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.zipArchive], []);
@@ -114,6 +120,12 @@
  * Tests zip file, with absolute paths, open (aka unzip) from Downloads.
  */
 testcase.zipFileOpenDownloadsWithAbsolutePaths = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchiveWithAbsolutePaths.targetPath],
+    openType: 'launch'
+  });
+
   // Open Files app on Downloads containing a zip file.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.zipArchiveWithAbsolutePaths], []);
@@ -153,6 +165,12 @@
  * Tests encrypted zip file open, and canceling the passphrase dialog.
  */
 testcase.zipFileOpenDownloadsEncryptedCancelPassphrase = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchiveEncrypted.targetPath],
+    openType: 'launch'
+  });
+
   const zipArchiverAppId = 'dmboannefpncccogfdikhmhpmdnddgoe';
   const zipArchiverPassphraseDialogUrl =
       'chrome-extension://dmboannefpncccogfdikhmhpmdnddgoe/html/passphrase.html';
@@ -253,6 +271,14 @@
  * Tests zip file open (aka unzip) from Google Drive.
  */
 testcase.zipFileOpenDrive = async () => {
+  if (await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.zipArchive.targetPath],
+      openType: 'launch'
+    });
+  }
+
   // Open Files app on Drive containing a zip file.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.zipArchive]);
@@ -278,6 +304,12 @@
  * Tests zip file open (aka unzip) from a removable USB volume.
  */
 testcase.zipFileOpenUsb = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchive.targetPath],
+    openType: 'launch'
+  });
+
   const USB_VOLUME_QUERY = '#directory-tree [volume-type-icon="removable"]';
 
   // Open Files app on Drive.
@@ -333,6 +365,12 @@
  * Tests creating a zip file on Downloads.
  */
 testcase.zipCreateFileDownloads = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.photos.targetPath],
+    openType: 'launch'
+  });
+
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -366,6 +404,14 @@
  * Tests creating a zip file on Drive.
  */
 testcase.zipCreateFileDrive = async () => {
+  if (await sendTestMessage({name: 'getDriveFsEnabled'}) === 'true') {
+    await sendTestMessage({
+      name: 'expectFileTask',
+      fileNames: [ENTRIES.photos.targetPath],
+      openType: 'launch'
+    });
+  }
+
   // Open Files app on Drive containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.photos]);
@@ -399,6 +445,12 @@
  * Tests creating a zip file on a removable USB volume.
  */
 testcase.zipCreateFileUsb = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.photos.targetPath],
+    openType: 'launch'
+  });
+
   const USB_VOLUME_QUERY = '#directory-tree [volume-type-icon="removable"]';
 
   // Open Files app on Drive.
@@ -452,6 +504,12 @@
  * The file names are encoded in SJIS.
  */
 testcase.zipFileOpenDownloadsShiftJIS = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchiveSJIS.targetPath],
+    openType: 'launch'
+  });
+
   // Open Files app on Downloads containing a zip file.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.zipArchiveSJIS], []);
@@ -493,6 +551,12 @@
  * is encoded in UTF-8, but the language encoding flag bit is set to 0.
  */
 testcase.zipFileOpenDownloadsMacOs = async () => {
+  await sendTestMessage({
+    name: 'expectFileTask',
+    fileNames: [ENTRIES.zipArchiveMacOs.targetPath],
+    openType: 'launch'
+  });
+
   // Open Files app on Downloads containing a zip file.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.zipArchiveMacOs], []);
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index 6bcb61d..c59a02b 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -284,7 +284,7 @@
   ui_->SetController(this);
   SetContainerBehaviorInternal(mojom::ContainerType::kFullWidth);
   ChangeState(KeyboardControllerState::INITIAL);
-  visual_bounds_in_screen_ = gfx::Rect();
+  visual_bounds_in_root_ = gfx::Rect();
   time_of_last_blur_ = base::Time::UnixEpoch();
   UpdateInputMethodObserver();
 
@@ -383,28 +383,29 @@
 
 // private
 void KeyboardController::NotifyKeyboardBoundsChanging(
-    const gfx::Rect& new_bounds) {
-  visual_bounds_in_screen_ = new_bounds;
+    const gfx::Rect& new_bounds_in_root) {
+  visual_bounds_in_root_ = new_bounds_in_root;
   aura::Window* window = GetKeyboardWindow();
   if (window && window->IsVisible()) {
-    const gfx::Rect occluded_bounds_in_screen = GetWorkspaceOccludedBounds();
+    const gfx::Rect occluded_bounds_in_root = GetWorkspaceOccludedBounds();
     notification_manager_.SendNotifications(
-        container_behavior_->OccludedBoundsAffectWorkspaceLayout(), new_bounds,
-        occluded_bounds_in_screen, observer_list_);
+        container_behavior_->OccludedBoundsAffectWorkspaceLayout(),
+        new_bounds_in_root, occluded_bounds_in_root, observer_list_);
   } else {
-    visual_bounds_in_screen_ = gfx::Rect();
+    visual_bounds_in_root_ = gfx::Rect();
   }
 
   EnsureCaretInWorkArea(GetWorkspaceOccludedBounds());
 }
 
-void KeyboardController::SetKeyboardWindowBounds(const gfx::Rect& new_bounds) {
+void KeyboardController::SetKeyboardWindowBounds(
+    const gfx::Rect& new_bounds_in_root) {
   ui::LayerAnimator* animator = GetKeyboardWindow()->layer()->GetAnimator();
   // Stops previous animation if a window resize is requested during animation.
   if (animator->is_animating())
     animator->StopAnimating();
 
-  GetKeyboardWindow()->SetBounds(new_bounds);
+  GetKeyboardWindow()->SetBounds(new_bounds_in_root);
 }
 
 void KeyboardController::NotifyKeyboardWindowLoaded() {
@@ -711,7 +712,7 @@
 
   // Notify observers after animation finished to prevent reveal desktop
   // background during animation.
-  NotifyKeyboardBoundsChanging(GetKeyboardWindow()->bounds());
+  NotifyKeyboardBoundsChanging(GetKeyboardWindow()->GetBoundsInRootWindow());
 }
 
 // private
@@ -750,6 +751,11 @@
   ShowKeyboardInternal(layout_delegate_->GetContainerForDisplay(display));
 }
 
+const gfx::Rect& KeyboardController::visual_bounds_in_screen() const {
+  // TODO(https://crbug.com/943446): Convert root window bounds to screen.
+  return visual_bounds_in_root_;
+}
+
 void KeyboardController::LoadKeyboardWindowInBackground() {
   DCHECK_EQ(state_, KeyboardControllerState::INITIAL);
 
@@ -774,8 +780,8 @@
 }
 
 void KeyboardController::EnsureCaretInWorkAreaForTest(
-    const gfx::Rect& occluded_bounds) {
-  EnsureCaretInWorkArea(occluded_bounds);
+    const gfx::Rect& occluded_bounds_in_root) {
+  EnsureCaretInWorkArea(occluded_bounds_in_root);
 }
 
 // ContainerBehavior::Delegate overrides
@@ -795,9 +801,9 @@
 
 void KeyboardController::MoveKeyboardWindowToDisplay(
     const display::Display& display,
-    const gfx::Rect& new_bounds) {
+    const gfx::Rect& new_bounds_in_root) {
   queued_display_change_ =
-      std::make_unique<QueuedDisplayChange>(display, new_bounds);
+      std::make_unique<QueuedDisplayChange>(display, new_bounds_in_root);
   HideKeyboardTemporarilyForTransition();
 }
 
@@ -810,21 +816,19 @@
 
 void KeyboardController::OnWindowBoundsChanged(
     aura::Window* window,
-    const gfx::Rect& old_bounds,
-    const gfx::Rect& new_bounds,
+    const gfx::Rect& old_bounds_in_root,
+    const gfx::Rect& new_bounds_in_root,
     ui::PropertyChangeReason reason) {
   if (!GetKeyboardWindow())
     return;
 
   // |window| could be the root window (for detecting screen rotations) or the
-  // keyboard window (for detecting keyboard bounds changes). For the root
-  // window, |new_bounds| is in screen coordinates. For the keyboard window,
-  // |new_bounds| is also in screen coordinates because VK container has
-  // kUsesScreenCoordinatesKey set.
+  // keyboard window (for detecting keyboard bounds changes).
   if (window == GetRootWindow())
-    container_behavior_->SetCanonicalBounds(GetKeyboardWindow(), new_bounds);
+    container_behavior_->SetCanonicalBounds(GetKeyboardWindow(),
+                                            new_bounds_in_root);
   else if (window == GetKeyboardWindow())
-    NotifyKeyboardBoundsChanging(new_bounds);
+    NotifyKeyboardBoundsChanging(new_bounds_in_root);
 }
 
 // InputMethodObserver overrides
@@ -1036,12 +1040,10 @@
   if (!ui_)
     return gfx::Rect();
 
-  const gfx::Rect visual_bounds_in_window(visual_bounds_in_screen_.size());
+  const gfx::Rect visual_bounds_in_window(visual_bounds_in_root_.size());
   const gfx::Rect occluded_bounds_in_window =
       container_behavior_->GetOccludedBounds(visual_bounds_in_window);
-  // Return occluded bounds that are relative to the screen.
-  return occluded_bounds_in_window +
-         visual_bounds_in_screen_.OffsetFromOrigin();
+  return occluded_bounds_in_window + visual_bounds_in_root_.OffsetFromOrigin();
 }
 
 gfx::Rect KeyboardController::GetKeyboardLockScreenOffsetBounds() const {
@@ -1051,7 +1053,7 @@
   if (!IsKeyboardOverscrollEnabled() &&
       container_behavior_->GetType() != mojom::ContainerType::kFloating &&
       container_behavior_->GetType() != mojom::ContainerType::kFullscreen) {
-    return visual_bounds_in_screen_;
+    return visual_bounds_in_root_;
   }
   return gfx::Rect();
 }
@@ -1061,23 +1063,23 @@
 
   // Notify that only the occluded bounds have changed.
   if (IsKeyboardVisible())
-    NotifyKeyboardBoundsChanging(visual_bounds_in_screen_);
+    NotifyKeyboardBoundsChanging(visual_bounds_in_root_);
 }
 
 void KeyboardController::SetHitTestBounds(
-    const std::vector<gfx::Rect>& bounds) {
+    const std::vector<gfx::Rect>& bounds_in_window) {
   if (!GetKeyboardWindow())
     return;
 
   GetKeyboardWindow()->SetEventTargeter(
-      std::make_unique<ShapedWindowTargeter>(bounds));
+      std::make_unique<ShapedWindowTargeter>(bounds_in_window));
 }
 
 gfx::Rect KeyboardController::AdjustSetBoundsRequest(
     const gfx::Rect& display_bounds,
-    const gfx::Rect& requested_bounds) const {
-  return container_behavior_->AdjustSetBoundsRequest(display_bounds,
-                                                     requested_bounds);
+    const gfx::Rect& requested_bounds_in_screen) const {
+  return container_behavior_->AdjustSetBoundsRequest(
+      display_bounds, requested_bounds_in_screen);
 }
 
 bool KeyboardController::IsOverscrollAllowed() const {
@@ -1092,7 +1094,7 @@
 
 void KeyboardController::SetContainerType(
     mojom::ContainerType type,
-    const base::Optional<gfx::Rect>& target_bounds,
+    const base::Optional<gfx::Rect>& target_bounds_in_root,
     base::OnceCallback<void(bool)> callback) {
   if (container_behavior_->GetType() == type) {
     std::move(callback).Run(false);
@@ -1103,14 +1105,14 @@
     // Keyboard is already shown. Hiding the keyboard at first then switching
     // container type.
     queued_container_type_ = std::make_unique<QueuedContainerType>(
-        this, type, target_bounds, std::move(callback));
+        this, type, target_bounds_in_root, std::move(callback));
     HideKeyboard(HIDE_REASON_SYSTEM_TEMPORARY);
   } else {
     // Keyboard is hidden. Switching the container type immediately and invoking
     // the passed callback now.
     SetContainerBehaviorInternal(type);
-    if (target_bounds)
-      SetKeyboardWindowBounds(target_bounds.value());
+    if (target_bounds_in_root)
+      SetKeyboardWindowBounds(*target_bounds_in_root);
     DCHECK_EQ(GetActiveContainerType(), type);
     std::move(callback).Run(true /* change_successful */);
   }
@@ -1172,17 +1174,20 @@
 }
 
 void KeyboardController::EnsureCaretInWorkArea(
-    const gfx::Rect& occluded_bounds) {
+    const gfx::Rect& occluded_bounds_in_root) {
   ui::InputMethod* ime = ui_->GetInputMethod();
   if (!ime)
     return;
 
   TRACE_EVENT0("vk", "EnsureCaretInWorkArea");
 
+  // TODO(https://crbug.com/943446): Convert root window bounds to screen.
+  auto occluded_bounds_in_screen = occluded_bounds_in_root;
+
   if (IsOverscrollAllowed()) {
-    ime->SetOnScreenKeyboardBounds(occluded_bounds);
+    ime->SetOnScreenKeyboardBounds(occluded_bounds_in_screen);
   } else if (ime->GetTextInputClient()) {
-    ime->GetTextInputClient()->EnsureCaretNotInRect(occluded_bounds);
+    ime->GetTextInputClient()->EnsureCaretNotInRect(occluded_bounds_in_screen);
   }
 }
 
diff --git a/ui/keyboard/keyboard_controller.h b/ui/keyboard/keyboard_controller.h
index baf8b6a..612c9ee 100644
--- a/ui/keyboard/keyboard_controller.h
+++ b/ui/keyboard/keyboard_controller.h
@@ -108,8 +108,8 @@
   // be null.
   void MoveToParentContainer(aura::Window* parent);
 
-  // Sets the bounds of the keyboard window.
-  void SetKeyboardWindowBounds(const gfx::Rect& new_bounds);
+  // Sets the bounds of the keyboard window, relative to the root window.
+  void SetKeyboardWindowBounds(const gfx::Rect& new_bounds_in_root);
 
   // Reloads the content of the keyboard. No-op if the keyboard content is not
   // loaded yet.
@@ -177,33 +177,35 @@
 
   // Returns the bounds in screen for the visible portion of the keyboard. An
   // empty rectangle will get returned when the keyboard is hidden.
-  const gfx::Rect& visual_bounds_in_screen() const {
-    return visual_bounds_in_screen_;
-  }
+  const gfx::Rect& visual_bounds_in_screen() const;
 
   // Returns the current bounds that affect the workspace layout. If the
   // keyboard is not shown or if the keyboard mode should not affect the usable
-  // region of the screen, an empty rectangle will be returned. Bounds are in
-  // screen coordinates.
+  // region of the screen, an empty rectangle will be returned. Bounds are
+  // relative to the root window.
   gfx::Rect GetWorkspaceOccludedBounds() const;
 
   // Returns the current bounds that affect the window layout of the various
-  // lock screens.
+  // lock screens. Bounds are relative to the root window.
   gfx::Rect GetKeyboardLockScreenOffsetBounds() const;
 
   // Set the area on the keyboard window that occlude whatever is behind it.
   void SetOccludedBounds(const gfx::Rect& bounds_in_window);
 
   // Set the areas on the keyboard window where events should be handled.
-  // Does not do anything if there is no keyboard window.
-  void SetHitTestBounds(const std::vector<gfx::Rect>& bounds);
+  // Does not do anything if there is no keyboard window. Bounds are relative to
+  // the keyboard window.
+  void SetHitTestBounds(const std::vector<gfx::Rect>& bounds_in_window);
 
   mojom::ContainerType GetActiveContainerType() const {
     return container_behavior_->GetType();
   }
 
-  gfx::Rect AdjustSetBoundsRequest(const gfx::Rect& display_bounds,
-                                   const gfx::Rect& requested_bounds) const;
+  // Adjusts |requested_bounds| according to the current container behavior.
+  // (e.g. prevent the keyboard from moving off screen).
+  gfx::Rect AdjustSetBoundsRequest(
+      const gfx::Rect& display_bounds,
+      const gfx::Rect& requested_bounds_in_screen) const;
 
   // Returns true if overscroll is currently allowed by the active keyboard
   // container behavior.
@@ -221,7 +223,7 @@
   // will trigger a hide animation and a subsequent show animation. Otherwise
   // the ContainerBehavior change is synchronous.
   void SetContainerType(mojom::ContainerType type,
-                        const base::Optional<gfx::Rect>& target_bounds,
+                        const base::Optional<gfx::Rect>& target_bounds_in_root,
                         base::OnceCallback<void(bool)> callback);
 
   // Sets floating keyboard draggable rect.
@@ -249,7 +251,7 @@
   }
   KeyboardControllerState GetStateForTest() const { return state_; }
   ui::InputMethod* GetInputMethodForTest();
-  void EnsureCaretInWorkAreaForTest(const gfx::Rect& occluded_bounds);
+  void EnsureCaretInWorkAreaForTest(const gfx::Rect& occluded_bounds_in_root);
 
  private:
   // For access to Observer methods for simulation.
@@ -362,7 +364,7 @@
 
   // Notifies observers that the visual or occluded bounds of the keyboard
   // window are changing.
-  void NotifyKeyboardBoundsChanging(const gfx::Rect& new_bounds);
+  void NotifyKeyboardBoundsChanging(const gfx::Rect& new_bounds_in_root);
 
   // Called when the keyboard window has loaded. Shows the keyboard if
   // |show_on_keyboard_window_load_| is true.
@@ -395,7 +397,7 @@
 
   // Ensures caret in current work area (not occluded by virtual keyboard
   // window).
-  void EnsureCaretInWorkArea(const gfx::Rect& occluded_bounds);
+  void EnsureCaretInWorkArea(const gfx::Rect& occluded_bounds_in_root);
 
   // Marks that the keyboard load has started. This is used to measure the time
   // it takes to fully load the keyboard. This should be called before
@@ -435,10 +437,10 @@
 
   base::ObserverList<KeyboardControllerObserver>::Unchecked observer_list_;
 
-  // The bounds in screen for the visible portion of the keyboard.
-  // If the keyboard window is visible, this should be the same size as the
-  // keyboard window. If not, this should be empty.
-  gfx::Rect visual_bounds_in_screen_;
+  // The bounds for the visible portion of the keyboard, relative to the root
+  // window. If the keyboard window is visible, this should be the same size as
+  // the keyboard window. If not, this should be empty.
+  gfx::Rect visual_bounds_in_root_;
 
   KeyboardControllerState state_ = KeyboardControllerState::UNKNOWN;
 
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index 7060a85..a07839da 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -406,6 +406,8 @@
           // In this case update check will be skipped and OOBE will
           // proceed straight to enrollment screen when EULA is accepted.
           chrome.send('skipUpdateEnrollAfterEula');
+        } else {
+          console.warn('No action for current step ID: ' + currentStepId);
         }
       } else if (name == ACCELERATOR_KIOSK_ENABLE) {
         if (attributes.toggleKioskAllowed ||
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index 0cf1032..e32e5a7f 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -361,13 +361,17 @@
 }
 
 void ScrollView::Layout() {
+  // When horizontal scrollbar is disabled, it should not matter
+  // if its OverlapsContent matches vertical bar's.
+  if (!hide_horizontal_scrollbar_) {
 #if defined(OS_MACOSX)
-  // On Mac, scrollbars may update their style one at a time, so they may
-  // temporarily be of different types. Refuse to lay out at this point.
-  if (horiz_sb_->OverlapsContent() != vert_sb_->OverlapsContent())
-    return;
+    // On Mac, scrollbars may update their style one at a time, so they may
+    // temporarily be of different types. Refuse to lay out at this point.
+    if (horiz_sb_->OverlapsContent() != vert_sb_->OverlapsContent())
+      return;
 #endif
-  DCHECK_EQ(horiz_sb_->OverlapsContent(), vert_sb_->OverlapsContent());
+    DCHECK_EQ(horiz_sb_->OverlapsContent(), vert_sb_->OverlapsContent());
+  }
 
   if (focus_ring_)
     focus_ring_->Layout();
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index ea07f7f..5f8b7a42 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -180,6 +180,38 @@
   DISALLOW_COPY_AND_ASSIGN(VerticalResizingView);
 };
 
+class TestScrollBarThumb : public BaseScrollBarThumb {
+ public:
+  using BaseScrollBarThumb::BaseScrollBarThumb;
+
+  // BaseScrollBarThumb:
+  gfx::Size CalculatePreferredSize() const override { return gfx::Size(1, 1); }
+  void OnPaint(gfx::Canvas* canvas) override {}
+};
+
+class TestScrollBar : public ScrollBar {
+ public:
+  TestScrollBar(bool horizontal, bool overlaps_content, int thickness)
+      : ScrollBar(horizontal),
+        overlaps_content_(overlaps_content),
+        thickness_(thickness) {
+    SetThumb(new TestScrollBarThumb(this));
+  }
+
+  // ScrollBar:
+  int GetThickness() const override { return thickness_; }
+  bool OverlapsContent() const override { return overlaps_content_; }
+  gfx::Rect GetTrackBounds() const override {
+    gfx::Rect bounds = GetLocalBounds();
+    bounds.set_width(GetThickness());
+    return bounds;
+  }
+
+ private:
+  const bool overlaps_content_ = false;
+  const int thickness_ = 0;
+};
+
 }  // namespace
 
 using test::ScrollViewTestApi;
@@ -1468,6 +1500,31 @@
   EXPECT_FALSE(test_api.more_content_right()->visible());
 }
 
+// Ensure ScrollView::Layout succeeds if a hidden scrollbar's overlap style
+// does not match the other scrollbar.
+TEST_F(ScrollViewTest, IgnoreOverlapWithHiddenHorizontalScroll) {
+  ScrollViewTestApi test_api(scroll_view_.get());
+
+  constexpr int kThickness = 1;
+  // Assume horizontal scroll bar is the default and is overlapping.
+  scroll_view_->SetHorizontalScrollBar(new TestScrollBar(
+      /* horizontal */ true, /* overlaps_content */ true, kThickness));
+  // Assume vertical scroll bar is custom and it we want it to not overlap.
+  scroll_view_->SetVerticalScrollBar(new TestScrollBar(
+      /* horizontal */ false, /* overlaps_content */ false, kThickness));
+
+  // Also, let's turn off horizontal scroll bar.
+  scroll_view_->set_hide_horizontal_scrollbar(true);
+
+  View* contents = InstallContents();
+  contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300));
+  scroll_view_->Layout();
+
+  gfx::Size expected_size = scroll_view_->size();
+  expected_size.Enlarge(-kThickness, 0);
+  EXPECT_EQ(expected_size, test_api.contents_viewport()->size());
+}
+
 // Test scrolling behavior when clicking on the scroll track.
 TEST_F(WidgetScrollViewTest, ScrollTrackScrolling) {
   // Set up with a vertical scroller.