diff --git a/DEPS b/DEPS
index 60b1fcbf..75a86ca 100644
--- a/DEPS
+++ b/DEPS
@@ -86,7 +86,7 @@
   # 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': 'ece125358ba245436769c56805edebc484a12ec5',
+  'angle_revision': '9af765dd2e4463242df8175f74d7a99af6f31cb1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -130,7 +130,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4b6b02f390d2c78fa7a98ebd9faa75942946f30f',
+  'catapult_revision': '9cfb34e845c8308d019a894be8c0926ee8cd3c91',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
diff --git a/ash/shelf/shelf.cc b/ash/shelf/shelf.cc
index 67f0c17..d217f341 100644
--- a/ash/shelf/shelf.cc
+++ b/ash/shelf/shelf.cc
@@ -21,6 +21,7 @@
 #include "ui/app_list/presenter/app_list.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/keyboard/keyboard_controller_observer.h"
 
 namespace ash {
 
@@ -289,6 +290,19 @@
 }
 
 void Shelf::SetVirtualKeyboardBoundsForTesting(const gfx::Rect& bounds) {
+  keyboard::KeyboardStateDescriptor state;
+  state.is_available = !bounds.IsEmpty();
+  state.is_locked = false;
+  state.visual_bounds = bounds;
+  state.occluded_bounds = bounds;
+  state.displaced_bounds = gfx::Rect();
+  shelf_layout_manager_->OnKeyboardAvailabilityChanging(state.is_available);
+  shelf_layout_manager_->OnKeyboardVisibleBoundsChanging(state.visual_bounds);
+  shelf_layout_manager_->OnKeyboardWorkspaceOccludedBoundsChanging(
+      state.occluded_bounds);
+  shelf_layout_manager_->OnKeyboardWorkspaceDisplacingBoundsChanging(
+      state.displaced_bounds);
+  shelf_layout_manager_->OnKeyboardAppearanceChanging(state);
   shelf_layout_manager_->OnKeyboardBoundsChanging(bounds);
 }
 
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index f0139a08..686503e 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -448,23 +448,21 @@
   UpdateAutoHideStateNow();
 }
 
-void ShelfLayoutManager::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) {
-  bool keyboard_is_about_to_hide = false;
-  if (new_bounds.IsEmpty() && !keyboard_bounds_.IsEmpty())
-    keyboard_is_about_to_hide = true;
-  // If in non-sticky mode, do not change the work area.
-  bool change_work_area =
-      keyboard::KeyboardController::GetInstance() &&
-      keyboard::KeyboardController::GetInstance()->keyboard_locked();
-
-  keyboard_bounds_ = new_bounds;
+void ShelfLayoutManager::OnKeyboardAppearanceChanging(
+    const keyboard::KeyboardStateDescriptor& state) {
+  // If in locked mode, change the work area.
+  bool change_work_area = state.is_locked;
+  keyboard_bounds_ = state.occluded_bounds;
   LayoutShelfAndUpdateBounds(change_work_area);
+}
 
+void ShelfLayoutManager::OnKeyboardAvailabilityChanging(
+    const bool is_available) {
   // On login screen if keyboard has been just hidden, update bounds just once
   // but ignore target_bounds.work_area_insets since shelf overlaps with login
   // window.
   if (Shell::Get()->session_controller()->IsUserSessionBlocked() &&
-      keyboard_is_about_to_hide) {
+      !is_available) {
     Shell::Get()->SetDisplayWorkAreaInsets(shelf_widget_->GetNativeWindow(),
                                            gfx::Insets());
   }
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 413df536..8336ac1 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -159,7 +159,9 @@
                          aura::Window* lost_active) override;
 
   // Overridden from keyboard::KeyboardControllerObserver:
-  void OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) override;
+  void OnKeyboardAppearanceChanging(
+      const keyboard::KeyboardStateDescriptor& state) override;
+  void OnKeyboardAvailabilityChanging(const bool is_available) override;
   void OnKeyboardClosed() override;
 
   // Overridden from LockStateObserver:
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index cb46600f..38b29f4 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -2113,6 +2113,19 @@
                              work_area.width(), work_area.height() / 2);
   }
 
+  void NotifyKeyboardChanging(ShelfLayoutManager* layout_manager,
+                              bool is_locked,
+                              const gfx::Rect& bounds) {
+    keyboard::KeyboardStateDescriptor state;
+    state.visual_bounds = bounds;
+    state.occluded_bounds = bounds;
+    state.displaced_bounds = is_locked ? bounds : gfx::Rect();
+    state.is_locked = is_locked;
+    state.is_available = !bounds.IsEmpty();
+    layout_manager->OnKeyboardAvailabilityChanging(state.is_available);
+    layout_manager->OnKeyboardAppearanceChanging(state);
+  }
+
   const gfx::Rect& keyboard_bounds() const { return keyboard_bounds_; }
 
  private:
@@ -2130,7 +2143,7 @@
       keyboard::KeyboardController::GetInstance();
   // Open keyboard in non-sticky mode.
   kb_controller->ShowKeyboard(false);
-  layout_manager->OnKeyboardBoundsChanging(keyboard_bounds());
+  NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());
   layout_manager->LayoutShelf();
 
   // Shelf position should not be changed.
@@ -2150,7 +2163,7 @@
 
   // Open keyboard in non-sticky mode.
   kb_controller->ShowKeyboard(false);
-  layout_manager->OnKeyboardBoundsChanging(keyboard_bounds());
+  NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());
   layout_manager->LayoutShelf();
 
   // Work area should not be changed.
@@ -2159,7 +2172,7 @@
 
   kb_controller->HideKeyboard(
       keyboard::KeyboardController::HIDE_REASON_AUTOMATIC);
-  layout_manager->OnKeyboardBoundsChanging(gfx::Rect());
+  NotifyKeyboardChanging(layout_manager, false, gfx::Rect());
   layout_manager->LayoutShelf();
   EXPECT_EQ(orig_work_area,
             display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
@@ -2176,7 +2189,7 @@
 
   // Open keyboard in sticky mode.
   kb_controller->ShowKeyboard(true);
-  layout_manager->OnKeyboardBoundsChanging(keyboard_bounds());
+  NotifyKeyboardChanging(layout_manager, true, keyboard_bounds());
   layout_manager->LayoutShelf();
 
   // Work area should be changed.
diff --git a/chrome/browser/extensions/api/automation/automation_apitest.cc b/chrome/browser/extensions/api/automation/automation_apitest.cc
index ede22c2..c196bed 100644
--- a/chrome/browser/extensions/api/automation/automation_apitest.cc
+++ b/chrome/browser/extensions/api/automation/automation_apitest.cc
@@ -329,6 +329,12 @@
       << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(AutomationApiTestWithDeviceScaleFactor, HitTest) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "hit_test.html"))
+      << message_;
+}
+
 #endif  // defined(OS_CHROMEOS)
 
 }  // namespace extensions
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
index 7bc9d53b..3f9b666 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.cc
@@ -23,6 +23,7 @@
 #include "ui/accessibility/platform/aura_window_properties.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
 #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -248,8 +249,11 @@
 
     content::RenderFrameHost* rfh =
         content::RenderFrameHost::FromAXTreeID(child_ax_tree_id);
-    if (rfh)
+    if (rfh) {
+      // Convert to pixels for the RenderFrameHost HitTest.
+      window->GetHost()->ConvertDIPToPixels(&action.target_point);
       rfh->AccessibilityPerformAction(action);
+    }
     return;
   }
 
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test.js
index 5745016..b29b09d7e 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/hit_test.js
@@ -5,7 +5,8 @@
 var allTests = [
   function testHitTestInDesktop() {
     var url = 'data:text/html,<!doctype html>' +
-        encodeURI('<button>Click Me</button>');
+        encodeURI('<div>Don\'t Click Me</div>' +
+                  '<button>Click Me</button>');
     var didHitTest = false;
     chrome.automation.getDesktop(function(desktop) {
       chrome.tabs.create({url: url});
@@ -14,14 +15,18 @@
         if (didHitTest)
           return;
         if (event.target.url.indexOf('data:') >= 0) {
-          var button = desktop.find({ attributes: { name: 'Click Me' } });
+          var button = desktop.find({ attributes: { name: 'Click Me',
+                                                    role: 'button' } });
           if (button) {
             didHitTest = true;
             button.addEventListener(EventType.ALERT, function() {
               chrome.test.succeed();
             }, true);
-            var cx = button.location.left + button.location.width / 2;
-            var cy = button.location.top + button.location.height / 2;
+            // Click just barely on the second button, very close
+            // to the first button. This tests that we are converting
+            // coordinate properly.
+            var cx = button.location.left + 10;
+            var cy = button.location.top + 10;
             desktop.hitTest(cx, cy, EventType.ALERT);
           }
         }
diff --git a/components/viz/service/display/copy_output_scaling_pixeltest.cc b/components/viz/service/display/copy_output_scaling_pixeltest.cc
index e76be2ce..ef9ac59 100644
--- a/components/viz/service/display/copy_output_scaling_pixeltest.cc
+++ b/components/viz/service/display/copy_output_scaling_pixeltest.cc
@@ -108,20 +108,47 @@
     std::unique_ptr<CopyOutputResult> result;
     {
       base::RunLoop loop;
-      std::unique_ptr<CopyOutputRequest> request(new CopyOutputRequest(
+
+      // Add a dummy copy request to be executed when the RED RenderPass is
+      // drawn (before the root RenderPass). This is a regression test to
+      // confirm GLRenderer state is consistent with the GL context after each
+      // copy request executes, and before the next RenderPass is drawn.
+      // http://crbug.com/792734
+      bool dummy_ran = false;
+      auto request = std::make_unique<CopyOutputRequest>(
           result_format_,
           base::BindOnce(
-              [](std::unique_ptr<CopyOutputResult>* test_result,
+              [](bool* dummy_ran, std::unique_ptr<CopyOutputResult> result) {
+                EXPECT_TRUE(!result->IsEmpty());
+                EXPECT_FALSE(*dummy_ran);
+                *dummy_ran = true;
+              },
+              &dummy_ran));
+      // Set a 10X zoom, which should be more than sufficient to disturb the
+      // results of the main copy request (below) if the GL state is not
+      // properly restored.
+      request->SetUniformScaleRatio(1, 10);
+      list.front()->copy_requests.push_back(std::move(request));
+
+      // Add a copy request to the root RenderPass, to capture the results of
+      // drawing all passes for this frame.
+      request = std::make_unique<CopyOutputRequest>(
+          result_format_,
+          base::BindOnce(
+              [](bool* dummy_ran,
+                 std::unique_ptr<CopyOutputResult>* test_result,
                  const base::Closure& quit_closure,
                  std::unique_ptr<CopyOutputResult> result_from_renderer) {
+                EXPECT_TRUE(*dummy_ran);
                 *test_result = std::move(result_from_renderer);
                 quit_closure.Run();
               },
-              &result, loop.QuitClosure())));
+              &dummy_ran, &result, loop.QuitClosure()));
       request->set_result_selection(
           copy_output::ComputeResultRect(copy_rect, scale_from_, scale_to_));
       request->SetScaleRatio(scale_from_, scale_to_);
       list.back()->copy_requests.push_back(std::move(request));
+
       renderer()->DrawFrame(&list, 1.0f, viewport_size);
       loop.Run();
     }
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index b342b165..f22e8f28 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -2686,6 +2686,10 @@
       GetFramebufferCopyTextureFormat(), framebuffer_texture,
       framebuffer_texture_size,
       current_frame()->current_render_pass->color_space);
+
+  // The copier modified texture/framebuffer bindings, shader programs, and
+  // other GL state; and so this must be restored before continuing.
+  RestoreGLState();
 }
 
 void GLRenderer::ToGLMatrix(float* gl_matrix, const gfx::Transform& transform) {
diff --git a/components/viz/service/display/gl_renderer_copier.h b/components/viz/service/display/gl_renderer_copier.h
index fe61431..a1f0fe9d 100644
--- a/components/viz/service/display/gl_renderer_copier.h
+++ b/components/viz/service/display/gl_renderer_copier.h
@@ -67,9 +67,10 @@
   // copy of the framebuffer. |color_space| specifies the color space of the
   // pixels in the framebuffer.
   //
-  // This implementation may change texture and framebuffer bindings, and so the
-  // caller must not make any assumptions about the original objects still being
-  // bound to the same units.
+  // This implementation may change a wide variety of GL state, such as texture
+  // and framebuffer bindings, shader programs, and related attributes; and so
+  // the caller must not make any assumptions about the state of the GL context
+  // after this call.
   void CopyFromTextureOrFramebuffer(std::unique_ptr<CopyOutputRequest> request,
                                     const gfx::Rect& output_rect,
                                     GLenum internal_format,
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 8602727c..e33919a 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -91,6 +91,7 @@
   "internal/cwv_web_view.mm",
   "internal/cwv_web_view_configuration.mm",
   "internal/cwv_web_view_configuration_internal.h",
+  "internal/cwv_web_view_internal.h",
   "internal/ios_global_state_web_view_configuration.cc",
   "internal/language/web_view_language_model_factory.cc",
   "internal/language/web_view_language_model_factory.h",
diff --git a/ios/web_view/internal/autofill/cwv_autofill_controller.mm b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
index 1ae3fecd..3f0a422 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_controller.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
@@ -302,6 +302,7 @@
 - (void)webStateDestroyed:(web::WebState*)webState {
   DCHECK_EQ(_webState, webState);
   [_autofillAgent detachFromWebState];
+  _autofillClient.reset();
   _webState->RemoveObserver(_webStateObserverBridge.get());
   _webStateObserverBridge.reset();
   _webState = nullptr;
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index 717825a6..7d7b2c7 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/web_view/public/cwv_web_view.h"
+#import "ios/web_view/internal/cwv_web_view_internal.h"
 
 #include <memory>
 #include <utility>
@@ -54,22 +54,6 @@
 }
 
 @interface CWVWebView ()<CRWWebStateDelegate, CRWWebStateObserver> {
-  // |_configuration| must come before |_webState| here to avoid crash on
-  // deallocating CWVWebView. This is because the destructor of |_webState|
-  // indirectly accesses the BrowserState instance, which is owned by
-  // |_configuration|.
-  //
-  // Looks like the fields of the object are deallocated in the reverse order of
-  // their definition. If |_webState| comes before |_configuration|, the order
-  // of execution is like this:
-  //
-  // 1. |_configuration| is deallocated
-  // 1.1. BrowserState is deallocated
-  // 2. |_webState| is deallocated
-  // 2.1. The destructor of |_webState| indirectly accesses the BrowserState
-  //      deallocated above, causing crash.
-  //
-  // See crbug.com/712556 for the full stack trace.
   CWVWebViewConfiguration* _configuration;
   std::unique_ptr<web::WebState> _webState;
   std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
@@ -99,7 +83,7 @@
 - (void)updateCurrentURLs;
 // Updates |title| property.
 - (void)updateTitle;
-// Returns a new CWVAutofillController created from |webState_|.
+// Returns a new CWVAutofillController created from |_webState|.
 - (CWVAutofillController*)newAutofillController;
 
 @end
@@ -158,6 +142,7 @@
   self = [super initWithFrame:frame];
   if (self) {
     _configuration = configuration;
+    [_configuration registerWebView:self];
     _scrollView = [[CWVScrollView alloc] init];
     [self resetWebStateWithSessionStorage:nil];
   }
@@ -216,8 +201,12 @@
   _javaScriptDialogPresenter->SetUIDelegate(_UIDelegate);
 }
 
-// -----------------------------------------------------------------------
-// WebStateObserver implementation.
+#pragma mark - CRWWebStateObserver
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+  webState->RemoveObserver(_webStateObserver.get());
+  _webStateObserver.reset();
+}
 
 - (void)webState:(web::WebState*)webState
     navigationItemsPruned:(size_t)pruned_item_count {
@@ -515,4 +504,10 @@
   self.title = base::SysUTF16ToNSString(_webState->GetTitle());
 }
 
+#pragma mark - Internal Methods
+
+- (void)shutDown {
+  _webState.reset();
+}
+
 @end
diff --git a/ios/web_view/internal/cwv_web_view_configuration.mm b/ios/web_view/internal/cwv_web_view_configuration.mm
index c1f6897..ec66f98 100644
--- a/ios/web_view/internal/cwv_web_view_configuration.mm
+++ b/ios/web_view/internal/cwv_web_view_configuration.mm
@@ -2,14 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/web_view/public/cwv_web_view_configuration.h"
 #import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
 
+#include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "ios/web_view/internal/app/application_context.h"
 #import "ios/web_view/internal/cwv_preferences_internal.h"
 #import "ios/web_view/internal/cwv_user_content_controller_internal.h"
+#import "ios/web_view/internal/cwv_web_view_internal.h"
 #include "ios/web_view/internal/web_view_browser_state.h"
 #include "ios/web_view/internal/web_view_global_state_util.h"
 
@@ -20,6 +21,12 @@
 @interface CWVWebViewConfiguration () {
   // The BrowserState for this configuration.
   std::unique_ptr<ios_web_view::WebViewBrowserState> _browserState;
+
+  // Holds all CWVWebViews created with this class. Weak references.
+  NSHashTable* _webViews;
+
+  // |YES| if |shutDown| was called.
+  BOOL _wasShutDown;
 }
 
 // Initializes configuration with the specified browser state mode.
@@ -32,8 +39,15 @@
 @synthesize preferences = _preferences;
 @synthesize userContentController = _userContentController;
 
+static CWVWebViewConfiguration* defaultConfiguration;
+static CWVWebViewConfiguration* incognitoConfiguration;
+
++ (void)shutDown {
+  [defaultConfiguration shutDown];
+  [incognitoConfiguration shutDown];
+}
+
 + (instancetype)defaultConfiguration {
-  static CWVWebViewConfiguration* defaultConfiguration;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
     auto browserState =
@@ -45,7 +59,6 @@
 }
 
 + (instancetype)incognitoConfiguration {
-  static CWVWebViewConfiguration* incognitoConfiguration;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
     auto browserState =
@@ -75,10 +88,16 @@
 
     _userContentController =
         [[CWVUserContentController alloc] initWithConfiguration:self];
+
+    _webViews = [NSHashTable weakObjectsHashTable];
   }
   return self;
 }
 
+- (void)dealloc {
+  DCHECK(_wasShutDown);
+}
+
 #pragma mark - Public Methods
 
 - (BOOL)isPersistent {
@@ -91,4 +110,16 @@
   return _browserState.get();
 }
 
+- (void)registerWebView:(CWVWebView*)webView {
+  [_webViews addObject:webView];
+}
+
+- (void)shutDown {
+  for (CWVWebView* webView in _webViews) {
+    [webView shutDown];
+  }
+  _browserState.reset();
+  _wasShutDown = YES;
+}
+
 @end
diff --git a/ios/web_view/internal/cwv_web_view_configuration_internal.h b/ios/web_view/internal/cwv_web_view_configuration_internal.h
index d7942d6..db63e2b 100644
--- a/ios/web_view/internal/cwv_web_view_configuration_internal.h
+++ b/ios/web_view/internal/cwv_web_view_configuration_internal.h
@@ -11,12 +11,26 @@
 class WebViewBrowserState;
 }  // namespace ios_web_view
 
+@class CWVWebView;
+
 @interface CWVWebViewConfiguration ()
 
+// Calls |shutDown| on the singletons returned by |defaultConfiguration| and
+// |incognitoConfiguration|.
++ (void)shutDown;
+
 // The browser state associated with this configuration.
 @property(nonatomic, readonly, nonnull)
     ios_web_view::WebViewBrowserState* browserState;
 
+// Registers a |webView| so that this class can call |shutDown| on it later on.
+// Only weak references are held, so no need for de-register method.
+- (void)registerWebView:(nonnull CWVWebView*)webView;
+
+// Because Obj-C classes under ARC tend to outlive C++ classes, this method is
+// needed to cleanly tear down this object. Must be called before |dealloc|.
+- (void)shutDown;
+
 @end
 
 #endif  // IOS_WEB_VIEW_INTERNAL_CWV_WEB_VIEW_CONFIGURATION_INTERNAL_H_
diff --git a/ios/web_view/internal/cwv_web_view_internal.h b/ios/web_view/internal/cwv_web_view_internal.h
new file mode 100644
index 0000000..07afa38
--- /dev/null
+++ b/ios/web_view/internal/cwv_web_view_internal.h
@@ -0,0 +1,18 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_VIEW_INTERNAL_CWV_WEB_VIEW_INTERNAL_H_
+#define IOS_WEB_VIEW_INTERNAL_CWV_WEB_VIEW_INTERNAL_H_
+
+#import "ios/web_view/public/cwv_web_view.h"
+
+@interface CWVWebView ()
+
+// This is called by the associated CWVWebViewConfiguration in order to shut
+// down cleanly. See CWVWebViewConfiguration's |shutDown| method for more info.
+- (void)shutDown;
+
+@end
+
+#endif  // IOS_WEB_VIEW_INTERNAL_CWV_WEB_VIEW_INTERNAL_H_
diff --git a/ios/web_view/internal/web_view_web_main_parts.mm b/ios/web_view/internal/web_view_web_main_parts.mm
index 79a7ee9..8f8e40ab 100644
--- a/ios/web_view/internal/web_view_web_main_parts.mm
+++ b/ios/web_view/internal/web_view_web_main_parts.mm
@@ -8,6 +8,7 @@
 #include "base/path_service.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "ios/web_view/internal/app/application_context.h"
+#import "ios/web_view/internal/cwv_web_view_configuration_internal.h"
 #include "ios/web_view/internal/translate/web_view_translate_service.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -52,6 +53,7 @@
 void WebViewWebMainParts::PostMainMessageLoopRun() {
   WebViewTranslateService::GetInstance()->Shutdown();
   ApplicationContext::GetInstance()->SaveState();
+  [CWVWebViewConfiguration shutDown];
 }
 
 void WebViewWebMainParts::PostDestroyThreads() {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 6486606..32cfa05 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -4034,7 +4034,8 @@
           "--upload-results",
           "--output-format=histograms",
           "--output-format=json-test-results",
-          "--browser=default"
+          "--browser=release",
+          "--xvfb"
         ],
         "isolate_name": "telemetry_perf_tests",
         "name": "memory.leak_detection",
@@ -6050,6 +6051,7 @@
       },
       {
         "args": [
+          "--gtest-filter-file=../../testing/buildbot/filters/site-per-process.content_browsertests.filter",
           "--site-per-process",
           "--test-launcher-filter-file=../../testing/buildbot/filters/site-per-process.content_browsertests.filter"
         ],
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 2721882f..44f04c2 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3368,6 +3368,9 @@
     'modifications': {
       # chromium.fyi
       'Site Isolation Android': {
+        'args': [
+          '--gtest-filter-file=../../testing/buildbot/filters/site-per-process.content_browsertests.filter',
+        ],
         'swarming': {
           'shards': 6,
         },
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 839ac5e..493a150 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -896,7 +896,8 @@
         '--upload-results',
         '--output-format=histograms',
         '--output-format=json-test-results',
-        '--browser=default',
+        '--browser=release',
+        '--xvfb'
       ],
       'isolate_name': 'telemetry_perf_tests',
       'override_compile_targets': [