diff --git a/DEPS b/DEPS
index 090f44c..8c15a55c 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'd689f7a5313a7fd501c2572c493fe6d91a88cba4',
+  'skia_revision': 'c86b1456f73eaf5a43fac8c920c7140993b2f0ba',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '85cebb78ba98bc5fc7ffb7c7a78521b4441df05b',
+  'v8_revision': '7fbbaba59ab53ff73965c0c71584cb467bd7b390',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -232,7 +232,7 @@
     Var('chromium_git') + '/native_client/src/third_party/scons-2.0.1.git' + '@' + '1c1550e17fc26355d08627fbdec13d8291227067',
 
   'src/third_party/webrtc':
-    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + '8ff7cecc5bc21065d2ca7ae4b65a2f28887466e0', # commit position 16354
+    Var('chromium_git') + '/external/webrtc/trunk/webrtc.git' + '@' + '2a4b1756ee43486e2bf1e0194a2d5850ce2ae050', # commit position 16404
 
   'src/third_party/openmax_dl':
     Var('chromium_git') + '/external/webrtc/deps/third_party/openmax.git' + '@' +  Var('openmax_dl_revision'),
diff --git a/ash/common/shelf/shelf_layout_manager.cc b/ash/common/shelf/shelf_layout_manager.cc
index d51f088..1639fba 100644
--- a/ash/common/shelf/shelf_layout_manager.cc
+++ b/ash/common/shelf/shelf_layout_manager.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/common/material_design/material_design_controller.h"
+#include "ash/common/session/session_controller.h"
 #include "ash/common/session/session_state_delegate.h"
 #include "ash/common/shelf/shelf_constants.h"
 #include "ash/common/shelf/shelf_layout_manager_observer.h"
@@ -95,7 +96,7 @@
 
   void OnImplicitAnimationsCompleted() override {
     if (shelf_)
-      shelf_->UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
+      shelf_->MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
     delete this;
   }
 
@@ -111,6 +112,31 @@
   DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
 };
 
+ShelfLayoutManager::State::State()
+    : visibility_state(SHELF_VISIBLE),
+      auto_hide_state(SHELF_AUTO_HIDE_HIDDEN),
+      window_state(wm::WORKSPACE_WINDOW_STATE_DEFAULT),
+      pre_lock_screen_animation_active(false),
+      session_state(session_manager::SessionState::UNKNOWN) {}
+
+bool ShelfLayoutManager::State::IsAddingSecondaryUser() const {
+  return session_state == session_manager::SessionState::LOGIN_SECONDARY;
+}
+
+bool ShelfLayoutManager::State::IsScreenLocked() const {
+  return session_state == session_manager::SessionState::LOCKED;
+}
+
+bool ShelfLayoutManager::State::Equals(const State& other) const {
+  return other.visibility_state == visibility_state &&
+         (visibility_state != SHELF_AUTO_HIDE ||
+          other.auto_hide_state == auto_hide_state) &&
+         other.window_state == window_state &&
+         other.pre_lock_screen_animation_active ==
+             pre_lock_screen_animation_active &&
+         other.session_state == session_state;
+}
+
 // ShelfLayoutManager ----------------------------------------------------------
 
 ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf_widget,
@@ -125,13 +151,16 @@
       gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
       update_shelf_observer_(NULL),
       chromevox_panel_height_(0),
-      duration_override_in_ms_(0) {
+      duration_override_in_ms_(0),
+      shelf_background_type_(SHELF_BACKGROUND_OVERLAP) {
   DCHECK(shelf_widget_);
   DCHECK(wm_shelf_);
   WmShell::Get()->AddShellObserver(this);
   WmShell::Get()->AddLockStateObserver(this);
   WmShell::Get()->AddActivationObserver(this);
-  WmShell::Get()->GetSessionStateDelegate()->AddSessionStateObserver(this);
+  WmShell::Get()->session_controller()->AddSessionStateObserver(this);
+  state_.session_state =
+      WmShell::Get()->session_controller()->GetSessionState();
 }
 
 ShelfLayoutManager::~ShelfLayoutManager() {
@@ -142,7 +171,7 @@
     observer.WillDeleteShelfLayoutManager();
   WmShell::Get()->RemoveShellObserver(this);
   WmShell::Get()->RemoveLockStateObserver(this);
-  WmShell::Get()->GetSessionStateDelegate()->RemoveSessionStateObserver(this);
+  WmShell::Get()->session_controller()->RemoveSessionStateObserver(this);
 }
 
 void ShelfLayoutManager::PrepareForShutdown() {
@@ -208,7 +237,7 @@
   WmWindow* shelf_window = WmLookup::Get()->GetWindowForWidget(shelf_widget_);
   if (in_shutdown_ || !wm_shelf_->IsShelfInitialized() || !shelf_window)
     return;
-  if (state_.is_screen_locked || state_.is_adding_user_screen) {
+  if (state_.IsScreenLocked() || state_.IsAddingSecondaryUser()) {
     SetState(SHELF_VISIBLE);
   } else if (WmShell::Get()->IsPinned()) {
     SetState(SHELF_HIDDEN);
@@ -296,7 +325,7 @@
 
 void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
   window_overlaps_shelf_ = value;
-  UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
+  MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
 }
 
 void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
@@ -365,13 +394,6 @@
   }
 }
 
-void ShelfLayoutManager::OnLockStateChanged(bool locked) {
-  // Force the shelf to layout for alignment (bottom if locked, restore
-  // the previous alignment otherwise).
-  state_.is_screen_locked = locked;
-  UpdateShelfVisibilityAfterLoginUIChange();
-}
-
 void ShelfLayoutManager::OnShelfAutoHideBehaviorChanged(WmWindow* root_window) {
   UpdateVisibilityState();
 }
@@ -418,15 +440,20 @@
 }
 
 ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
+  if (state_.pre_lock_screen_animation_active)
+    return SHELF_BACKGROUND_DEFAULT;
+
+  // Handle all non active screen states, including OOBE and pre-login.
+  if (state_.session_state != session_manager::SessionState::ACTIVE)
+    return SHELF_BACKGROUND_OVERLAP;
+
   if (state_.visibility_state != SHELF_AUTO_HIDE &&
       state_.window_state == wm::WORKSPACE_WINDOW_STATE_MAXIMIZED) {
     return SHELF_BACKGROUND_MAXIMIZED;
   }
 
   if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
-      (!state_.is_screen_locked && !state_.is_adding_user_screen &&
-       window_overlaps_shelf_) ||
-      (state_.visibility_state == SHELF_AUTO_HIDE)) {
+      window_overlaps_shelf_ || state_.visibility_state == SHELF_AUTO_HIDE) {
     return SHELF_BACKGROUND_OVERLAP;
   }
 
@@ -456,8 +483,9 @@
   state.window_state = controller ? controller->GetWorkspaceWindowState()
                                   : wm::WORKSPACE_WINDOW_STATE_DEFAULT;
   // Preserve the log in screen states.
-  state.is_adding_user_screen = state_.is_adding_user_screen;
-  state.is_screen_locked = state_.is_screen_locked;
+  state.session_state = state_.session_state;
+  state.pre_lock_screen_animation_active =
+      state_.pre_lock_screen_animation_active;
 
   // Force an update because gesture drags affect the shelf bounds and we
   // should animate back to the normal bounds at the end of a gesture.
@@ -501,10 +529,10 @@
   if (delay_background_change) {
     if (update_shelf_observer_)
       update_shelf_observer_->Detach();
-    // UpdateShelfBackground deletes itself when the animation is done.
+    // |update_shelf_observer_| deletes itself when the animation is done.
     update_shelf_observer_ = new UpdateShelfObserver(this);
   } else {
-    UpdateShelfBackground(change_type);
+    MaybeUpdateShelfBackground(change_type);
   }
 
   TargetBounds target_bounds;
@@ -583,14 +611,14 @@
 
     // For crbug.com/622431, when the shelf alignment is BOTTOM_LOCKED, we
     // don't set display work area, as it is not real user-set alignment.
-    if (!state_.is_screen_locked &&
+    if (!state_.IsScreenLocked() &&
         shelf_widget_->GetAlignment() != SHELF_ALIGNMENT_BOTTOM_LOCKED &&
         change_work_area) {
       gfx::Insets insets;
       // If user session is blocked (login to new user session or add user to
       // the existing session - multi-profile) then give 100% of work area only
       // if keyboard is not shown.
-      if (!state_.is_adding_user_screen || !keyboard_bounds_.IsEmpty())
+      if (!state_.IsAddingSecondaryUser() || !keyboard_bounds_.IsEmpty())
         insets = target_bounds.work_area_insets;
       WmShell::Get()->SetDisplayWorkAreaInsets(shelf_window, insets);
     }
@@ -790,11 +818,16 @@
   }
 }
 
-void ShelfLayoutManager::UpdateShelfBackground(
+void ShelfLayoutManager::MaybeUpdateShelfBackground(
     BackgroundAnimatorChangeType type) {
-  const ShelfBackgroundType background_type(GetShelfBackgroundType());
+  const ShelfBackgroundType new_background_type(GetShelfBackgroundType());
+
+  if (new_background_type == shelf_background_type_)
+    return;
+
+  shelf_background_type_ = new_background_type;
   for (auto& observer : observers_)
-    observer.OnBackgroundUpdated(background_type, type);
+    observer.OnBackgroundUpdated(shelf_background_type_, type);
 }
 
 void ShelfLayoutManager::UpdateAutoHideStateNow() {
@@ -952,7 +985,7 @@
     dock_bounds_ = dock_bounds;
     OnWindowResized();
     UpdateVisibilityState();
-    UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
+    MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
   }
 }
 
@@ -960,21 +993,32 @@
   if (event == EVENT_LOCK_ANIMATION_STARTED) {
     // Enter the screen locked state and update the visibility to avoid an odd
     // animation when transitioning the orientation from L/R to bottom.
-    state_.is_screen_locked = true;
+    state_.pre_lock_screen_animation_active = true;
     UpdateShelfVisibilityAfterLoginUIChange();
+  } else {
+    state_.pre_lock_screen_animation_active = false;
   }
+  MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
 }
 
 void ShelfLayoutManager::SessionStateChanged(
     session_manager::SessionState state) {
   // Check transition changes to/from the add user to session and change the
   // shelf alignment accordingly
-  const bool add_user = state == session_manager::SessionState::LOGIN_SECONDARY;
-  if (add_user != state_.is_adding_user_screen) {
-    state_.is_adding_user_screen = add_user;
+  const bool was_adding_user = state_.IsAddingSecondaryUser();
+  const bool was_locked = state_.IsScreenLocked();
+  state_.session_state = state;
+  MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
+  if (was_adding_user != state_.IsAddingSecondaryUser()) {
     UpdateShelfVisibilityAfterLoginUIChange();
     return;
   }
+
+  // Force the shelf to layout for alignment (bottom if locked, restore the
+  // previous alignment otherwise).
+  if (was_locked != state_.IsScreenLocked())
+    UpdateShelfVisibilityAfterLoginUIChange();
+
   TargetBounds target_bounds;
   CalculateTargetBounds(state_, &target_bounds);
   UpdateBoundsAndOpacity(target_bounds, true /* animate */,
@@ -1020,7 +1064,7 @@
   gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE
                                       ? auto_hide_state()
                                       : SHELF_AUTO_HIDE_SHOWN;
-  UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
+  MaybeUpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
 }
 
 void ShelfLayoutManager::UpdateGestureDrag(const ui::GestureEvent& gesture) {
diff --git a/ash/common/shelf/shelf_layout_manager.h b/ash/common/shelf/shelf_layout_manager.h
index e0eda19..9f2834b3 100644
--- a/ash/common/shelf/shelf_layout_manager.h
+++ b/ash/common/shelf/shelf_layout_manager.h
@@ -132,7 +132,6 @@
                       const gfx::Rect& requested_bounds) override;
 
   // Overridden from ShellObserver:
-  void OnLockStateChanged(bool locked) override;
   void OnShelfAutoHideBehaviorChanged(WmWindow* root_window) override;
   void OnPinnedStateChanged(WmWindow* pinned_window) override;
 
@@ -178,7 +177,7 @@
   // Is the shelf's alignment horizontal?
   bool IsHorizontalAlignment() const;
 
-  // Returns how the shelf background is painted.
+  // Returns how the shelf background should be painted.
   ShelfBackgroundType GetShelfBackgroundType() const;
 
   // Set the height of the ChromeVox panel, which takes away space from the
@@ -204,31 +203,25 @@
   };
 
   struct State {
-    State()
-        : visibility_state(SHELF_VISIBLE),
-          auto_hide_state(SHELF_AUTO_HIDE_HIDDEN),
-          window_state(wm::WORKSPACE_WINDOW_STATE_DEFAULT),
-          is_screen_locked(false),
-          is_adding_user_screen(false) {}
+    State();
+
+    // Returns true when a secondary user is being added to an existing session.
+    bool IsAddingSecondaryUser() const;
+
+    bool IsScreenLocked() const;
 
     // Returns true if the two states are considered equal. As
     // |auto_hide_state| only matters if |visibility_state| is
     // |SHELF_AUTO_HIDE|, Equals() ignores the |auto_hide_state| as
     // appropriate.
-    bool Equals(const State& other) const {
-      return other.visibility_state == visibility_state &&
-             (visibility_state != SHELF_AUTO_HIDE ||
-              other.auto_hide_state == auto_hide_state) &&
-             other.window_state == window_state &&
-             other.is_screen_locked == is_screen_locked &&
-             other.is_adding_user_screen == is_adding_user_screen;
-    }
+    bool Equals(const State& other) const;
 
     ShelfVisibilityState visibility_state;
     ShelfAutoHideState auto_hide_state;
     wm::WorkspaceWindowState window_state;
-    bool is_screen_locked;
-    bool is_adding_user_screen;
+    // True when the system is in the cancelable, pre-lock screen animation.
+    bool pre_lock_screen_animation_active;
+    session_manager::SessionState session_state;
   };
 
   // Sets the visibility of the shelf to |state|.
@@ -253,8 +246,8 @@
   // used by |CalculateTargetBounds()|.
   void UpdateTargetBoundsForGesture(TargetBounds* target_bounds) const;
 
-  // Updates the background of the shelf.
-  void UpdateShelfBackground(BackgroundAnimatorChangeType type);
+  // Updates the background of the shelf if it has changed.
+  void MaybeUpdateShelfBackground(BackgroundAnimatorChangeType change_type);
 
   // Updates the auto hide state immediately.
   void UpdateAutoHideStateNow();
@@ -365,6 +358,10 @@
   // The show hide animation duration override or 0 for default.
   int duration_override_in_ms_;
 
+  // The current shelf background. Should not be assigned to directly, use
+  // MaybeUpdateShelfBackground() instead.
+  ShelfBackgroundType shelf_background_type_;
+
   DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager);
 };
 
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index d3424f0..9cafd20 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -8,7 +8,7 @@
 #include "ash/common/accelerators/accelerator_table.h"
 #include "ash/common/focus_cycler.h"
 #include "ash/common/material_design/material_design_controller.h"
-#include "ash/common/session/session_state_delegate.h"
+#include "ash/common/session/session_controller.h"
 #include "ash/common/shelf/shelf_constants.h"
 #include "ash/common/shelf/shelf_layout_manager_observer.h"
 #include "ash/common/shelf/shelf_view.h"
@@ -26,6 +26,7 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/test/test_app_list_view_presenter_impl.h"
 #include "ash/test/test_system_tray_item.h"
+#include "ash/wm/lock_state_controller.h"
 #include "ash/wm/window_state_aura.h"
 #include "ash/wm/window_util.h"
 #include "base/command_line.h"
@@ -302,16 +303,16 @@
 
   // Turn on the lock screen.
   void LockScreen() {
-    WmShell::Get()->GetSessionStateDelegate()->LockScreen();
-    // The test session state delegate does not fire the lock state change.
-    Shell::GetInstance()->OnLockStateChanged(true);
+    mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+    info->state = session_manager::SessionState::LOCKED;
+    ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
   }
 
   // Turn off the lock screen.
   void UnlockScreen() {
-    WmShell::Get()->GetSessionStateDelegate()->UnlockScreen();
-    // The test session state delegate does not fire the lock state change.
-    Shell::GetInstance()->OnLockStateChanged(false);
+    mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+    info->state = session_manager::SessionState::ACTIVE;
+    ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
   }
 
  private:
@@ -841,6 +842,18 @@
   EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
 }
 
+// Assertions around the login screen.
+TEST_F(ShelfLayoutManagerTest, VisibleWhenLoginScreenShowing) {
+  WmShelf* shelf = GetPrimaryShelf();
+
+  mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+  info->state = session_manager::SessionState::LOGIN_PRIMARY;
+  ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
+
+  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+}
+
 // Assertions around the lock screen showing.
 TEST_F(ShelfLayoutManagerTest, VisibleWhenLockScreenShowing) {
   WmShelf* shelf = GetPrimaryShelf();
@@ -866,9 +879,11 @@
   LockScreen();
   // Showing a widget in the lock screen should force the shelf to be visibile.
   EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
 
   UnlockScreen();
   EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
 }
 
 // Assertions around SetAutoHideBehavior.
@@ -1562,7 +1577,22 @@
   }
 }
 
+TEST_F(ShelfLayoutManagerTest, BackgroundTypeWhenLockingScreen) {
+  EXPECT_NE(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+
+  Shell::GetInstance()
+      ->lock_state_controller()
+      ->StartLockAnimationAndLockImmediately(false);
+  EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+}
+
 TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColor) {
+  // TODO(bruthig|xiyuan): Move SessionState setup into AshTestBase or
+  // AshTestHelper.
+  mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+  info->state = session_manager::SessionState::ACTIVE;
+  ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
+
   EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
 
   std::unique_ptr<aura::Window> w1(CreateTestWindow());
@@ -1595,6 +1625,12 @@
 // Verify that the shelf doesn't have the opaque background if it's auto-hide
 // status.
 TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColorAutoHide) {
+  // TODO(bruthig|xiyuan): Move SessionState setup into AshTestBase or
+  // AshTestHelper.
+  mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+  info->state = session_manager::SessionState::ACTIVE;
+  ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
+
   EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
 
   GetPrimaryShelf()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
diff --git a/ash/wm/workspace_controller_unittest.cc b/ash/wm/workspace_controller_unittest.cc
index c45f8bb2..b5c66ae 100644
--- a/ash/wm/workspace_controller_unittest.cc
+++ b/ash/wm/workspace_controller_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <map>
 
+#include "ash/common/session/session_controller.h"
 #include "ash/common/shelf/shelf_layout_manager.h"
 #include "ash/common/shelf/shelf_widget.h"
 #include "ash/common/shelf/wm_shelf.h"
@@ -14,6 +15,7 @@
 #include "ash/common/wm/panels/panel_layout_manager.h"
 #include "ash/common/wm/window_state.h"
 #include "ash/common/wm/workspace/workspace_window_resizer.h"
+#include "ash/common/wm_shell.h"
 #include "ash/common/wm_window.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/screen_util.h"
@@ -506,6 +508,12 @@
 // Verifies going from maximized to minimized sets the right state for painting
 // the background of the launcher.
 TEST_P(WorkspaceControllerTest, MinimizeResetsVisibility) {
+  // TODO(bruthig|xiyuan): Move SessionState setup into AshTestBase or
+  // AshTestHelper.
+  mojom::SessionInfoPtr info = mojom::SessionInfo::New();
+  info->state = session_manager::SessionState::ACTIVE;
+  ash::WmShell::Get()->session_controller()->SetSessionInfo(std::move(info));
+
   std::unique_ptr<Window> w1(CreateTestWindow());
   w1->Show();
   wm::ActivateWindow(w1.get());
diff --git a/base/android/java/src/org/chromium/base/library_loader/ModernLinker.java b/base/android/java/src/org/chromium/base/library_loader/ModernLinker.java
index 7669e0ee..db03e03 100644
--- a/base/android/java/src/org/chromium/base/library_loader/ModernLinker.java
+++ b/base/android/java/src/org/chromium/base/library_loader/ModernLinker.java
@@ -251,8 +251,6 @@
             Log.i(TAG, "disableSharedRelros() called");
         }
         synchronized (mLock) {
-            assert !mPrepareLibraryLoadCalled;
-
             // Mark this as a service process, and disable wait for shared RELRO.
             mInBrowserProcess = false;
             mWaitForSharedRelros = false;
diff --git a/base/cpu.cc b/base/cpu.cc
index 2532816..848208f7 100644
--- a/base/cpu.cc
+++ b/base/cpu.cc
@@ -16,7 +16,6 @@
 
 #if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
 #include "base/files/file_util.h"
-#include "base/lazy_instance.h"
 #endif
 
 #if defined(ARCH_CPU_X86_FAMILY)
@@ -94,9 +93,8 @@
 #endif  // ARCH_CPU_X86_FAMILY
 
 #if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
-class LazyCpuInfoValue {
- public:
-  LazyCpuInfoValue() {
+std::string* CpuInfoBrand() {
+  static std::string* brand = []() {
     // This function finds the value from /proc/cpuinfo under the key "model
     // name" or "Processor". "model name" is used in Linux 3.8 and later (3.7
     // and later for arm64) and is shown once per CPU. "Processor" is used in
@@ -109,30 +107,23 @@
     ReadFileToString(FilePath("/proc/cpuinfo"), &contents);
     DCHECK(!contents.empty());
     if (contents.empty()) {
-      return;
+      return new std::string();
     }
 
     std::istringstream iss(contents);
     std::string line;
     while (std::getline(iss, line)) {
-      if (brand_.empty() &&
-          (line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0 ||
+      if ((line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0 ||
            line.compare(0, strlen(kProcessorPrefix), kProcessorPrefix) == 0)) {
-        brand_.assign(line.substr(strlen(kModelNamePrefix)));
+        return new std::string(line.substr(strlen(kModelNamePrefix)));
       }
     }
-  }
 
-  const std::string& brand() const { return brand_; }
+    return new std::string();
+  }();
 
- private:
-  std::string brand_;
-  DISALLOW_COPY_AND_ASSIGN(LazyCpuInfoValue);
-};
-
-base::LazyInstance<LazyCpuInfoValue>::Leaky g_lazy_cpuinfo =
-    LAZY_INSTANCE_INITIALIZER;
-
+  return brand;
+}
 #endif  // defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) ||
         // defined(OS_LINUX))
 
@@ -221,7 +212,7 @@
     has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0;
   }
 #elif defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
-  cpu_brand_.assign(g_lazy_cpuinfo.Get().brand());
+  cpu_brand_.assign(*CpuInfoBrand());
 #endif
 }
 
diff --git a/base/debug/close_handle_hook_win.cc b/base/debug/close_handle_hook_win.cc
index 168de67..1f2aeab 100644
--- a/base/debug/close_handle_hook_win.cc
+++ b/base/debug/close_handle_hook_win.cc
@@ -12,7 +12,6 @@
 #include <memory>
 #include <vector>
 
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/win/iat_patch_function.h"
 #include "base/win/pe_image.h"
@@ -197,13 +196,11 @@
 
   void AddIATPatch(HMODULE module);
   void AddEATPatch();
-  void Unpatch();
 
  private:
   std::vector<base::win::IATPatchFunction*> hooks_;
   DISALLOW_COPY_AND_ASSIGN(HandleHooks);
 };
-base::LazyInstance<HandleHooks> g_hooks = LAZY_INSTANCE_INITIALIZER;
 
 void HandleHooks::AddIATPatch(HMODULE module) {
   if (!module)
@@ -232,14 +229,6 @@
            reinterpret_cast<void**>(&g_duplicate_function));
 }
 
-void HandleHooks::Unpatch() {
-  for (std::vector<base::win::IATPatchFunction*>::iterator it = hooks_.begin();
-       it != hooks_.end(); ++it) {
-    (*it)->Unpatch();
-    delete *it;
-  }
-}
-
 void PatchLoadedModules(HandleHooks* hooks) {
   const DWORD kSize = 256;
   DWORD returned;
@@ -259,7 +248,7 @@
 }  // namespace
 
 void InstallHandleHooks() {
-  HandleHooks* hooks = g_hooks.Pointer();
+  static HandleHooks* hooks = new HandleHooks();
 
   // Performing EAT interception first is safer in the presence of other
   // threads attempting to call CloseHandle.
@@ -267,10 +256,5 @@
   PatchLoadedModules(hooks);
 }
 
-void RemoveHandleHooks() {
-  // We are partching all loaded modules without forcing them to stay in memory,
-  // removing patches is not safe.
-}
-
 }  // namespace debug
 }  // namespace base
diff --git a/base/debug/close_handle_hook_win.h b/base/debug/close_handle_hook_win.h
index a042467..c775d75 100644
--- a/base/debug/close_handle_hook_win.h
+++ b/base/debug/close_handle_hook_win.h
@@ -13,9 +13,6 @@
 // Installs the hooks required to debug use of improper handles.
 BASE_EXPORT void InstallHandleHooks();
 
-// Removes the hooks installed by InstallHandleHooks().
-BASE_EXPORT void RemoveHandleHooks();
-
 }  // namespace debug
 }  // namespace base
 
diff --git a/base/memory/memory_pressure_listener.cc b/base/memory/memory_pressure_listener.cc
index 27e70018..a421c18b 100644
--- a/base/memory/memory_pressure_listener.cc
+++ b/base/memory/memory_pressure_listener.cc
@@ -4,7 +4,6 @@
 
 #include "base/memory/memory_pressure_listener.h"
 
-#include "base/lazy_instance.h"
 #include "base/observer_list_threadsafe.h"
 #include "base/trace_event/trace_event.h"
 
@@ -51,8 +50,10 @@
   DISALLOW_COPY_AND_ASSIGN(MemoryPressureObserver);
 };
 
-LazyInstance<MemoryPressureObserver>::Leaky g_observer =
-    LAZY_INSTANCE_INITIALIZER;
+MemoryPressureObserver* GetMemoryPressureObserver() {
+  static auto observer = new MemoryPressureObserver();
+  return observer;
+}
 
 subtle::Atomic32 g_notifications_suppressed = 0;
 
@@ -61,7 +62,7 @@
 MemoryPressureListener::MemoryPressureListener(
     const MemoryPressureListener::MemoryPressureCallback& callback)
     : callback_(callback) {
-  g_observer.Get().AddObserver(this, false);
+  GetMemoryPressureObserver()->AddObserver(this, false);
 }
 
 MemoryPressureListener::MemoryPressureListener(
@@ -70,11 +71,11 @@
         sync_memory_pressure_callback)
     : callback_(callback),
       sync_memory_pressure_callback_(sync_memory_pressure_callback) {
-  g_observer.Get().AddObserver(this, true);
+  GetMemoryPressureObserver()->AddObserver(this, true);
 }
 
 MemoryPressureListener::~MemoryPressureListener() {
-  g_observer.Get().RemoveObserver(this);
+  GetMemoryPressureObserver()->RemoveObserver(this);
 }
 
 void MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) {
@@ -123,7 +124,7 @@
     MemoryPressureLevel memory_pressure_level) {
   DCHECK_NE(memory_pressure_level, MEMORY_PRESSURE_LEVEL_NONE);
 
-  g_observer.Get().Notify(memory_pressure_level);
+  GetMemoryPressureObserver()->Notify(memory_pressure_level);
 }
 
 }  // namespace base
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 2212941d..72185a8 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_pump_default.h"
@@ -38,10 +37,11 @@
 namespace {
 
 // A lazily created thread local storage for quick access to a thread's message
-// loop, if one exists.  This should be safe and free of static constructors.
-LazyInstance<base::ThreadLocalPointer<MessageLoop> >::Leaky lazy_tls_ptr =
-    LAZY_INSTANCE_INITIALIZER;
-
+// loop, if one exists.
+base::ThreadLocalPointer<MessageLoop>* GetTLSMessageLoop() {
+  static auto lazy_tls_ptr = new base::ThreadLocalPointer<MessageLoop>();
+  return lazy_tls_ptr;
+}
 MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = NULL;
 
 #if defined(OS_IOS)
@@ -137,7 +137,7 @@
 
   // OK, now make it so that no one can find us.
   if (current() == this)
-    lazy_tls_ptr.Pointer()->Set(nullptr);
+    GetTLSMessageLoop()->Set(nullptr);
 }
 
 // static
@@ -145,7 +145,7 @@
   // TODO(darin): sadly, we cannot enable this yet since people call us even
   // when they have no intention of using us.
   // DCHECK(loop) << "Ouch, did you forget to initialize me?";
-  return lazy_tls_ptr.Pointer()->Get();
+  return GetTLSMessageLoop()->Get();
 }
 
 // static
@@ -338,7 +338,7 @@
     pump_ = CreateMessagePumpForType(type_);
 
   DCHECK(!current()) << "should only have one message loop per thread";
-  lazy_tls_ptr.Pointer()->Set(this);
+  GetTLSMessageLoop()->Set(this);
 
   incoming_task_queue_->StartScheduling();
   unbound_task_runner_->BindToCurrentThread();
diff --git a/base/path_service.cc b/base/path_service.cc
index 3f954d79..17ba0d7 100644
--- a/base/path_service.cc
+++ b/base/path_service.cc
@@ -13,7 +13,6 @@
 #include "base/containers/hash_tables.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
@@ -129,10 +128,9 @@
   }
 };
 
-static LazyInstance<PathData>::Leaky g_path_data = LAZY_INSTANCE_INITIALIZER;
-
 static PathData* GetPathData() {
-  return g_path_data.Pointer();
+  static auto path_data = new PathData();
+  return path_data;
 }
 
 // Tries to find |key| in the cache. |path_data| should be locked by the caller!
diff --git a/base/threading/thread_local_storage.cc b/base/threading/thread_local_storage.cc
index 15a1d5e..198a308 100644
--- a/base/threading/thread_local_storage.cc
+++ b/base/threading/thread_local_storage.cc
@@ -5,7 +5,6 @@
 #include "base/threading/thread_local_storage.h"
 
 #include "base/atomicops.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
@@ -90,9 +89,12 @@
   uint32_t version;
 };
 
-// This LazyInstance isn't needed until after we've constructed the per-thread
-// TLS vector, so it's safe to use.
-base::LazyInstance<base::Lock>::Leaky g_tls_metadata_lock;
+// This lock isn't needed until after we've constructed the per-thread TLS
+// vector, so it's safe to use.
+base::Lock* GetTLSMetadataLock() {
+  static auto lock = new base::Lock();
+  return lock;
+}
 TlsMetadata g_tls_metadata[kThreadLocalStorageSize];
 size_t g_last_assigned_slot = 0;
 
@@ -182,7 +184,7 @@
   // Snapshot the TLS Metadata so we don't have to lock on every access.
   TlsMetadata tls_metadata[kThreadLocalStorageSize];
   {
-    base::AutoLock auto_lock(g_tls_metadata_lock.Get());
+    base::AutoLock auto_lock(*GetTLSMetadataLock());
     memcpy(tls_metadata, g_tls_metadata, sizeof(g_tls_metadata));
   }
 
@@ -261,7 +263,7 @@
   slot_ = kInvalidSlotValue;
   version_ = 0;
   {
-    base::AutoLock auto_lock(g_tls_metadata_lock.Get());
+    base::AutoLock auto_lock(*GetTLSMetadataLock());
     for (int i = 0; i < kThreadLocalStorageSize; ++i) {
       // Tracking the last assigned slot is an attempt to find the next
       // available slot within one iteration. Under normal usage, slots remain
@@ -291,7 +293,7 @@
   DCHECK_NE(slot_, kInvalidSlotValue);
   DCHECK_LT(slot_, kThreadLocalStorageSize);
   {
-    base::AutoLock auto_lock(g_tls_metadata_lock.Get());
+    base::AutoLock auto_lock(*GetTLSMetadataLock());
     g_tls_metadata[slot_].status = TlsStatus::FREE;
     g_tls_metadata[slot_].destructor = nullptr;
     ++(g_tls_metadata[slot_].version);
diff --git a/base/threading/watchdog.cc b/base/threading/watchdog.cc
index c0637998..a34c132 100644
--- a/base/threading/watchdog.cc
+++ b/base/threading/watchdog.cc
@@ -5,7 +5,6 @@
 #include "base/threading/watchdog.h"
 
 #include "base/compiler_specific.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/threading/platform_thread.h"
 
@@ -31,7 +30,10 @@
   TimeDelta last_debugged_alarm_delay;
 };
 
-LazyInstance<StaticData>::Leaky g_static_data = LAZY_INSTANCE_INITIALIZER;
+StaticData* GetStaticData() {
+  static auto static_data = new StaticData();
+  return static_data;
+}
 
 }  // namespace
 
@@ -119,7 +121,7 @@
 void Watchdog::ThreadDelegate::ThreadMain() {
   SetThreadName();
   TimeDelta remaining_duration;
-  StaticData* static_data = g_static_data.Pointer();
+  StaticData* static_data = GetStaticData();
   while (1) {
     AutoLock lock(watchdog_->lock_);
     while (DISARMED == watchdog_->state_)
@@ -175,7 +177,7 @@
 
 // static
 void Watchdog::ResetStaticData() {
-  StaticData* static_data = g_static_data.Pointer();
+  StaticData* static_data = GetStaticData();
   AutoLock lock(static_data->lock);
   static_data->last_debugged_alarm_time = TimeTicks();
   static_data->last_debugged_alarm_delay = TimeDelta();
diff --git a/base/threading/worker_pool.cc b/base/threading/worker_pool.cc
index 6e07b6ef..96a39148 100644
--- a/base/threading/worker_pool.cc
+++ b/base/threading/worker_pool.cc
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/debug/leak_annotations.h"
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/task_runner.h"
 #include "base/threading/post_task_and_reply_impl.h"
@@ -98,9 +97,6 @@
   scoped_refptr<TaskRunner> taskrunners_[2];
 };
 
-base::LazyInstance<TaskRunnerHolder>::Leaky
-    g_taskrunners = LAZY_INSTANCE_INITIALIZER;
-
 }  // namespace
 
 bool WorkerPool::PostTaskAndReply(const tracked_objects::Location& from_here,
@@ -120,7 +116,8 @@
 // static
 const scoped_refptr<TaskRunner>&
 WorkerPool::GetTaskRunner(bool tasks_are_slow) {
-  return g_taskrunners.Get().taskrunners_[tasks_are_slow];
+  static auto task_runner_holder = new TaskRunnerHolder();
+  return task_runner_holder->taskrunners_[tasks_are_slow];
 }
 
 }  // namespace base
diff --git a/base/threading/worker_pool_win.cc b/base/threading/worker_pool_win.cc
index d638c0f..1b16bdc 100644
--- a/base/threading/worker_pool_win.cc
+++ b/base/threading/worker_pool_win.cc
@@ -16,21 +16,23 @@
 
 namespace {
 
-base::LazyInstance<ThreadLocalBoolean>::Leaky
-    g_worker_pool_running_on_this_thread = LAZY_INSTANCE_INITIALIZER;
+ThreadLocalBoolean* GetWorkerPoolRunningOnThisThread() {
+  static auto thread_local_boolean = new ThreadLocalBoolean();
+  return thread_local_boolean;
+}
 
 DWORD CALLBACK WorkItemCallback(void* param) {
   PendingTask* pending_task = static_cast<PendingTask*>(param);
   TRACE_TASK_EXECUTION("WorkerThread::ThreadMain::Run", *pending_task);
 
-  g_worker_pool_running_on_this_thread.Get().Set(true);
+  GetWorkerPoolRunningOnThisThread()->Set(true);
 
   tracked_objects::TaskStopwatch stopwatch;
   stopwatch.Start();
   std::move(pending_task->task).Run();
   stopwatch.Stop();
 
-  g_worker_pool_running_on_this_thread.Get().Set(false);
+  GetWorkerPoolRunningOnThisThread()->Set(false);
 
   tracked_objects::ThreadData::TallyRunOnWorkerThreadIfTracking(
       pending_task->birth_tally, pending_task->time_posted, stopwatch);
@@ -65,7 +67,7 @@
 
 // static
 bool WorkerPool::RunsTasksOnCurrentThread() {
-  return g_worker_pool_running_on_this_thread.Get().Get();
+  return GetWorkerPoolRunningOnThisThread()->Get();
 }
 
 }  // namespace base
diff --git a/base/time/time_posix.cc b/base/time/time_posix.cc
index 963c15b..ada2058 100644
--- a/base/time/time_posix.cc
+++ b/base/time/time_posix.cc
@@ -26,7 +26,6 @@
 #endif
 
 #if !defined(OS_MACOSX)
-#include "base/lazy_instance.h"
 #include "base/synchronization/lock.h"
 #endif
 
@@ -35,8 +34,10 @@
 #if !defined(OS_MACOSX)
 // This prevents a crash on traversing the environment global and looking up
 // the 'TZ' variable in libc. See: crbug.com/390567.
-base::LazyInstance<base::Lock>::Leaky
-    g_sys_time_to_time_struct_lock = LAZY_INSTANCE_INITIALIZER;
+base::Lock* GetSysTimeToTimeStructLock() {
+  static auto lock = new base::Lock();
+  return lock;
+}
 
 // Define a system-specific SysTime that wraps either to a time_t or
 // a time64_t depending on the host system, and associated convertion.
@@ -45,7 +46,7 @@
 typedef time64_t SysTime;
 
 SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
-  base::AutoLock locked(g_sys_time_to_time_struct_lock.Get());
+  base::AutoLock locked(*GetSysTimeToTimeStructLock());
   if (is_local)
     return mktime64(timestruct);
   else
@@ -53,7 +54,7 @@
 }
 
 void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
-  base::AutoLock locked(g_sys_time_to_time_struct_lock.Get());
+  base::AutoLock locked(*GetSysTimeToTimeStructLock());
   if (is_local)
     localtime64_r(&t, timestruct);
   else
@@ -64,7 +65,7 @@
 typedef time_t SysTime;
 
 SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
-  base::AutoLock locked(g_sys_time_to_time_struct_lock.Get());
+  base::AutoLock locked(*GetSysTimeToTimeStructLock());
   if (is_local)
     return mktime(timestruct);
   else
@@ -72,7 +73,7 @@
 }
 
 void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
-  base::AutoLock locked(g_sys_time_to_time_struct_lock.Get());
+  base::AutoLock locked(*GetSysTimeToTimeStructLock());
   if (is_local)
     localtime_r(&t, timestruct);
   else
diff --git a/base/time/time_win.cc b/base/time/time_win.cc
index 19144cb..75f398d6 100644
--- a/base/time/time_win.cc
+++ b/base/time/time_win.cc
@@ -40,7 +40,6 @@
 #include "base/atomicops.h"
 #include "base/bit_cast.h"
 #include "base/cpu.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/platform_thread.h"
@@ -97,8 +96,10 @@
 // How many times the high resolution timer has been called.
 uint32_t g_high_res_timer_count = 0;
 // The lock to control access to the above two variables.
-base::LazyInstance<base::Lock>::Leaky g_high_res_lock =
-    LAZY_INSTANCE_INITIALIZER;
+base::Lock* GetHighResLock() {
+  static auto lock = new base::Lock();
+  return lock;
+}
 
 // Returns the current value of the performance counter.
 uint64_t QPCNowRaw() {
@@ -191,7 +192,7 @@
 
 // static
 void Time::EnableHighResolutionTimer(bool enable) {
-  base::AutoLock lock(g_high_res_lock.Get());
+  base::AutoLock lock(*GetHighResLock());
   if (g_high_res_timer_enabled == enable)
     return;
   g_high_res_timer_enabled = enable;
@@ -218,7 +219,7 @@
   // called.
   const uint32_t max = std::numeric_limits<uint32_t>::max();
 
-  base::AutoLock lock(g_high_res_lock.Get());
+  base::AutoLock lock(*GetHighResLock());
   UINT period = g_high_res_timer_enabled ? kMinTimerIntervalHighResMs
                                          : kMinTimerIntervalLowResMs;
   if (activating) {
@@ -237,7 +238,7 @@
 
 // static
 bool Time::IsHighResolutionTimerInUse() {
-  base::AutoLock lock(g_high_res_lock.Get());
+  base::AutoLock lock(*GetHighResLock());
   return g_high_res_timer_enabled && g_high_res_timer_count > 0;
 }
 
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index edfd648..54e4cfee 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -13,7 +13,6 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/debug/leak_annotations.h"
-#include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
@@ -89,14 +88,10 @@
 #define MAX_TRACE_EVENT_FILTERS 32
 
 // List of TraceEventFilter objects from the most recent tracing session.
-base::LazyInstance<std::vector<std::unique_ptr<TraceEventFilter>>>::Leaky
-    g_category_group_filters = LAZY_INSTANCE_INITIALIZER;
-
-// The name of the current thread. This is used to decide if the current
-// thread name has changed. We combine all the seen thread names into the
-// output name for the thread.
-LazyInstance<ThreadLocalPointer<const char>>::Leaky g_current_thread_name =
-    LAZY_INSTANCE_INITIALIZER;
+std::vector<std::unique_ptr<TraceEventFilter>>& GetCategoryGroupFilters() {
+  static auto filters = new std::vector<std::unique_ptr<TraceEventFilter>>();
+  return *filters;
+}
 
 ThreadTicks ThreadNow() {
   return ThreadTicks::IsSupported() ? ThreadTicks::Now() : ThreadTicks();
@@ -169,8 +164,8 @@
       CategoryRegistry::GetCategoryByStatePtr(category_group_enabled);
   uint32_t filter_bitmap = category->enabled_filters();
   for (int index = 0; filter_bitmap != 0; filter_bitmap >>= 1, index++) {
-    if (filter_bitmap & 1 && g_category_group_filters.Get()[index])
-      filter_fn(g_category_group_filters.Get()[index].get());
+    if (filter_bitmap & 1 && GetCategoryGroupFilters()[index])
+      filter_fn(GetCategoryGroupFilters()[index].get());
   }
 }
 
@@ -473,7 +468,7 @@
   for (const auto& event_filter : enabled_event_filters_) {
     if (event_filter.IsCategoryGroupEnabled(category->name())) {
       state_flags |= TraceCategory::ENABLED_FOR_FILTERING;
-      DCHECK(g_category_group_filters.Get()[index]);
+      DCHECK(GetCategoryGroupFilters()[index]);
       enabled_filters_bitmap |= 1 << index;
     }
     if (index++ >= MAX_TRACE_EVENT_FILTERS) {
@@ -499,11 +494,11 @@
 
   // Filters were already added and tracing could be enabled. Filters list
   // cannot be changed when trace events are using them.
-  if (g_category_group_filters.Get().size())
+  if (GetCategoryGroupFilters().size())
     return;
 
   for (auto& filter_config : enabled_event_filters_) {
-    if (g_category_group_filters.Get().size() >= MAX_TRACE_EVENT_FILTERS) {
+    if (GetCategoryGroupFilters().size() >= MAX_TRACE_EVENT_FILTERS) {
       NOTREACHED()
           << "Too many trace event filters installed in the current session";
       break;
@@ -522,7 +517,7 @@
         new_filter = filter_factory_for_testing_(predicate_name);
       CHECK(new_filter) << "Unknown trace filter " << predicate_name;
     }
-    g_category_group_filters.Get().push_back(std::move(new_filter));
+    GetCategoryGroupFilters().push_back(std::move(new_filter));
   }
 }
 
@@ -589,7 +584,7 @@
     // cleared at the end of tracing because some threads which hit trace event
     // when disabling, could try to use the filters.
     if (!enabled_modes_)
-      g_category_group_filters.Get().clear();
+      GetCategoryGroupFilters().clear();
 
     // Update trace config for recording.
     const bool already_recording = enabled_modes_ & RECORDING_MODE;
@@ -1220,9 +1215,9 @@
     // call (if any), but don't bother if the new name is empty. Note this will
     // not detect a thread name change within the same char* buffer address: we
     // favor common case performance over corner case correctness.
-    if (new_name != g_current_thread_name.Get().Get() && new_name &&
-        *new_name) {
-      g_current_thread_name.Get().Set(new_name);
+    static auto current_thread_name = new ThreadLocalPointer<const char>();
+    if (new_name != current_thread_name->Get() && new_name && *new_name) {
+      current_thread_name->Set(new_name);
 
       AutoLock thread_info_lock(thread_info_lock_);
 
diff --git a/base/win/scoped_handle.cc b/base/win/scoped_handle.cc
index 6d152aec..47ab854 100644
--- a/base/win/scoped_handle.cc
+++ b/base/win/scoped_handle.cc
@@ -11,7 +11,6 @@
 #include "base/debug/alias.h"
 #include "base/debug/stack_trace.h"
 #include "base/hash.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/synchronization/lock_impl.h"
@@ -43,10 +42,13 @@
 };
 typedef std::unordered_map<HANDLE, Info, HandleHash> HandleMap;
 
-// g_lock protects the handle map and setting g_active_verifier within this
+// GetLock() protects the handle map and setting g_active_verifier within this
 // module.
 typedef base::internal::LockImpl NativeLock;
-base::LazyInstance<NativeLock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
+NativeLock* GetLock() {
+  static auto native_lock = new NativeLock();
+  return native_lock;
+}
 
 // Simple automatic locking using a native critical section so it supports
 // recursive locking.
@@ -70,9 +72,7 @@
 // way to delete this object from the wrong side of it (or any side, actually).
 class ActiveVerifier {
  public:
-  explicit ActiveVerifier(bool enabled)
-      : enabled_(enabled), lock_(g_lock.Pointer()) {
-  }
+  explicit ActiveVerifier(bool enabled) : enabled_(enabled), lock_(GetLock()) {}
 
   // Retrieves the current verifier.
   static ActiveVerifier* Get();
@@ -117,11 +117,11 @@
   return true;
 }
 
-// Assigns the g_active_verifier global within the g_lock lock.
+// Assigns the g_active_verifier global within the GetLock() lock.
 // If |existing_verifier| is non-null then |enabled| is ignored.
 void ThreadSafeAssignOrCreateActiveVerifier(ActiveVerifier* existing_verifier,
                                             bool enabled) {
-  AutoNativeLock lock(g_lock.Get());
+  AutoNativeLock lock(*GetLock());
   // Another thread in this module might be trying to assign the global
   // verifier, so check that within the lock here.
   if (g_active_verifier)
diff --git a/base/win/win_util.cc b/base/win/win_util.cc
index 3b7d319..a1b7f89 100644
--- a/base/win/win_util.cc
+++ b/base/win/win_util.cc
@@ -32,7 +32,6 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/strings/string_util.h"
@@ -67,44 +66,6 @@
   *((volatile int*)0) = 0x1337;
 }
 
-typedef decltype(GetProcessMitigationPolicy)* GetProcessMitigationPolicyType;
-
-class LazyIsUser32AndGdi32Available {
- public:
-  LazyIsUser32AndGdi32Available() : value_(!IsWin32kSyscallsDisabled()) {}
-
-  ~LazyIsUser32AndGdi32Available() {}
-
-  bool value() { return value_; }
-
- private:
-  static bool IsWin32kSyscallsDisabled() {
-    // Can't disable win32k prior to windows 8.
-    if (base::win::GetVersion() < base::win::VERSION_WIN8)
-      return false;
-
-    GetProcessMitigationPolicyType get_process_mitigation_policy_func =
-        reinterpret_cast<GetProcessMitigationPolicyType>(GetProcAddress(
-            GetModuleHandle(L"kernel32.dll"), "GetProcessMitigationPolicy"));
-
-    if (!get_process_mitigation_policy_func)
-      return false;
-
-    PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {};
-    if (get_process_mitigation_policy_func(GetCurrentProcess(),
-                                           ProcessSystemCallDisablePolicy,
-                                           &policy, sizeof(policy))) {
-      return policy.DisallowWin32kSystemCalls != 0;
-    }
-
-    return false;
-  }
-
-  const bool value_;
-
-  DISALLOW_COPY_AND_ASSIGN(LazyIsUser32AndGdi32Available);
-};
-
 // Returns the current platform role. We use the PowerDeterminePlatformRoleEx
 // API for that.
 POWER_PLATFORM_ROLE GetPlatformRole() {
@@ -540,9 +501,32 @@
 }
 
 bool IsUser32AndGdi32Available() {
-  static base::LazyInstance<LazyIsUser32AndGdi32Available>::Leaky available =
-      LAZY_INSTANCE_INITIALIZER;
-  return available.Get().value();
+  static auto is_user32_and_gdi32_available = []() {
+    // If win32k syscalls aren't disabled, then user32 and gdi32 are available.
+
+    // Can't disable win32k prior to windows 8.
+    if (base::win::GetVersion() < base::win::VERSION_WIN8)
+      return true;
+
+    typedef decltype(
+        GetProcessMitigationPolicy)* GetProcessMitigationPolicyType;
+    GetProcessMitigationPolicyType get_process_mitigation_policy_func =
+        reinterpret_cast<GetProcessMitigationPolicyType>(GetProcAddress(
+            GetModuleHandle(L"kernel32.dll"), "GetProcessMitigationPolicy"));
+
+    if (!get_process_mitigation_policy_func)
+      return true;
+
+    PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {};
+    if (get_process_mitigation_policy_func(GetCurrentProcess(),
+                                           ProcessSystemCallDisablePolicy,
+                                           &policy, sizeof(policy))) {
+      return policy.DisallowWin32kSystemCalls == 0;
+    }
+
+    return true;
+  }();
+  return is_user32_and_gdi32_available;
 }
 
 bool GetLoadedModulesSnapshot(HANDLE process, std::vector<HMODULE>* snapshot) {
diff --git a/build/config/sanitizers/sanitizers.gni b/build/config/sanitizers/sanitizers.gni
index 512b44b..a0e9940 100644
--- a/build/config/sanitizers/sanitizers.gni
+++ b/build/config/sanitizers/sanitizers.gni
@@ -139,7 +139,7 @@
   asan_globals = !is_mac
 }
 
-if ((use_afl || use_libfuzzer) && sanitizer_coverage_flags == "") {
+if (use_afl && sanitizer_coverage_flags == "") {
   sanitizer_coverage_flags = "trace-pc-guard"
 } else if (use_sanitizer_coverage && sanitizer_coverage_flags == "") {
   sanitizer_coverage_flags = "edge,indirect-calls,8bit-counters"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/compositor/OWNERS
index c853dcd..c0500c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/OWNERS
@@ -1,4 +1,3 @@
 changwan@chromium.org
 dtrainor@chromium.org
-
-per-file Compositor*=mdjones@chromium.org
+mdjones@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
index 3e69224..ddcb2e39 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
@@ -20,24 +20,22 @@
 
 /**
  * Item that allows the user to perform an action on the NTP.
- * Note: Use {@link #refreshVisibility()} to update the visibility of the button instead of calling
- * {@link #setVisible(boolean)} directly.
  */
 public class ActionItem extends OptionalLeaf {
-    @IntDef({ACTION_NONE, ACTION_VIEW_ALL, ACTION_FETCH_MORE, ACTION_RELOAD})
+    @IntDef({ACTION_NONE, ACTION_VIEW_ALL, ACTION_FETCH})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {}
     public static final int ACTION_NONE = 0;
     public static final int ACTION_VIEW_ALL = 1;
-    public static final int ACTION_FETCH_MORE = 2;
-    public static final int ACTION_RELOAD = 3;
+    public static final int ACTION_FETCH = 2;
 
     private final SuggestionsCategoryInfo mCategoryInfo;
     private final SuggestionsSection mParentSection;
     private final SuggestionsRanker mSuggestionsRanker;
 
     @Action
-    private int mCurrentAction = ACTION_NONE;
+    private final int mCurrentAction;
+
     private boolean mImpressionTracked;
     private int mPerSectionRank = -1;
 
@@ -45,10 +43,6 @@
         mCategoryInfo = section.getCategoryInfo();
         mParentSection = section;
         mSuggestionsRanker = ranker;
-    }
-
-    /** Call this instead of {@link #setVisible(boolean)} to update the visibility. */
-    public void refreshVisibility() {
         mCurrentAction = findAppropriateAction();
         setVisible(mCurrentAction != ACTION_NONE);
     }
@@ -86,8 +80,7 @@
             case ACTION_VIEW_ALL:
                 mCategoryInfo.performViewAllAction(uiDelegate.getNavigationDelegate());
                 return;
-            case ACTION_FETCH_MORE:
-            case ACTION_RELOAD:
+            case ACTION_FETCH:
                 uiDelegate.getSuggestionsSource().fetchSuggestions(
                         mCategoryInfo.getCategory(), mParentSection.getDisplayedSuggestionIds());
                 mParentSection.onFetchStarted();
@@ -101,10 +94,8 @@
 
     @Action
     private int findAppropriateAction() {
-        boolean hasSuggestions = mParentSection.hasSuggestions();
         if (mCategoryInfo.hasViewAllAction()) return ACTION_VIEW_ALL;
-        if (hasSuggestions && mCategoryInfo.hasFetchMoreAction()) return ACTION_FETCH_MORE;
-        if (!hasSuggestions && mCategoryInfo.hasReloadAction()) return ACTION_RELOAD;
+        if (mCategoryInfo.hasFetchAction()) return ACTION_FETCH;
         return ACTION_NONE;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
index 7f333ef2..8145913f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsCategoryInfo.java
@@ -39,18 +39,11 @@
     private final int mCardLayout;
 
     /**
-     * Whether the category supports a "More" action, that triggers fetching more suggestions for
-     * the category, while keeping the current ones.
+     * Whether the category supports a "Fetch" action, that triggers fetching more suggestions for
+     * the category.
      * @see ActionItem
      */
-    private final boolean mHasFetchMoreAction;
-
-    /**
-     * Whether the category supports a "Reload" action, that triggers fetching new suggestions to
-     * replace the current ones.
-     * @see ActionItem
-     */
-    private final boolean mHasReloadAction;
+    private final boolean mHasFetchAction;
 
     /**
      * Whether the category supports a "ViewAll" action, that triggers displaying all the content
@@ -69,14 +62,12 @@
     private final String mNoSuggestionsMessage;
 
     public SuggestionsCategoryInfo(@CategoryInt int category, String title,
-            @ContentSuggestionsCardLayoutEnum int cardLayout, boolean hasMoreAction,
-            boolean hasReloadAction, boolean hasViewAllAction, boolean showIfEmpty,
-            String noSuggestionsMessage) {
+            @ContentSuggestionsCardLayoutEnum int cardLayout, boolean hasFetchAction,
+            boolean hasViewAllAction, boolean showIfEmpty, String noSuggestionsMessage) {
         mCategory = category;
         mTitle = title;
         mCardLayout = cardLayout;
-        mHasFetchMoreAction = hasMoreAction;
-        mHasReloadAction = hasReloadAction;
+        mHasFetchAction = hasFetchAction;
         mHasViewAllAction = hasViewAllAction;
         mShowIfEmpty = showIfEmpty;
         mNoSuggestionsMessage = noSuggestionsMessage;
@@ -96,12 +87,8 @@
         return mCardLayout;
     }
 
-    public boolean hasFetchMoreAction() {
-        return mHasFetchMoreAction;
-    }
-
-    public boolean hasReloadAction() {
-        return mHasReloadAction;
+    public boolean hasFetchAction() {
+        return mHasFetchAction;
     }
 
     public boolean hasViewAllAction() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
index b3eca40..e5bebb4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
@@ -242,7 +242,6 @@
 
     private void refreshChildrenVisibility() {
         mStatus.setVisible(!hasSuggestions());
-        mMoreButton.refreshVisibility();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
index 8f99716..716439a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -286,10 +286,10 @@
 
     @CalledByNative
     private static SuggestionsCategoryInfo createSuggestionsCategoryInfo(int category, String title,
-            int cardLayout, boolean hasMoreAction, boolean hasReloadAction,
-            boolean hasViewAllAction, boolean showIfEmpty, String noSuggestionsMessage) {
-        return new SuggestionsCategoryInfo(category, title, cardLayout, hasMoreAction,
-                hasReloadAction, hasViewAllAction, showIfEmpty, noSuggestionsMessage);
+            int cardLayout, boolean hasFetchAction, boolean hasViewAllAction, boolean showIfEmpty,
+            String noSuggestionsMessage) {
+        return new SuggestionsCategoryInfo(category, title, cardLayout, hasFetchAction,
+                hasViewAllAction, showIfEmpty, noSuggestionsMessage);
     }
 
     @CalledByNative
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/NewTabPageRecyclerViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/NewTabPageRecyclerViewTest.java
index 0c3c0d0..c66f2ed01 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/NewTabPageRecyclerViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/cards/NewTabPageRecyclerViewTest.java
@@ -65,9 +65,8 @@
         mSource = new FakeSuggestionsSource();
         mSource.setInfoForCategory(KnownCategories.ARTICLES,
                 new SuggestionsCategoryInfo(KnownCategories.ARTICLES, "Articles test title",
-                        ContentSuggestionsCardLayout.FULL_CARD, /*hasMoreAction=*/true,
-                        /*hasReloadAction=*/true, /*hasViewAllAction=*/false, /*showIfEmpty=*/true,
-                        "noSuggestionsMessage"));
+                        ContentSuggestionsCardLayout.FULL_CARD, /*hasFetchAction=*/true,
+                        /*hasViewAllAction=*/false, /*showIfEmpty=*/true, "noSuggestionsMessage"));
         mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.INITIALIZING);
         NewTabPage.setSuggestionsSourceForTests(mSource);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
index 035184630c..b0bb71a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
@@ -156,16 +156,20 @@
 
         mSnippetsSource.setInfoForCategory(
                 fullCategory, new SuggestionsCategoryInfo(fullCategory, "Section Title",
-                                      ContentSuggestionsCardLayout.FULL_CARD, false, false, false,
-                                      true, "No suggestions"));
+                                      ContentSuggestionsCardLayout.FULL_CARD,
+                                      /*has_fetch_action=*/false,
+                                      /*has_view_all_action=*/false,
+                                      /*show_if_empty=*/true, "No suggestions"));
         mSnippetsSource.setStatusForCategory(fullCategory, CategoryStatus.AVAILABLE);
         mSnippetsSource.setSuggestionsForCategory(
                 fullCategory, Arrays.asList(shortSnippet, longSnippet));
 
         mSnippetsSource.setInfoForCategory(
                 minimalCategory, new SuggestionsCategoryInfo(minimalCategory, "Section Title",
-                                         ContentSuggestionsCardLayout.MINIMAL_CARD, false, false,
-                                         false, true, "No suggestions"));
+                                         ContentSuggestionsCardLayout.MINIMAL_CARD,
+                                         /* has_fetch_action = */ false,
+                                         /* has_view_all_action = */ false,
+                                         /* show_if_empty = */ true, "No suggestions"));
         mSnippetsSource.setStatusForCategory(minimalCategory, CategoryStatus.AVAILABLE);
         mSnippetsSource.setSuggestionsForCategory(
                 minimalCategory, Arrays.asList(minimalSnippet, minimalSnippet2));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
index b0f9295..2dcf99b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsTestUtils.java
@@ -47,7 +47,7 @@
             @CategoryInt int category, int suggestionCount) {
         // Important: showIfEmpty flag to true.
         SuggestionsCategoryInfo categoryInfo =
-                new CategoryInfoBuilder(category).withReloadAction().showIfEmpty().build();
+                new CategoryInfoBuilder(category).withFetchAction().showIfEmpty().build();
         return registerCategory(suggestionsSource, categoryInfo, suggestionCount);
     }
 
@@ -101,9 +101,8 @@
     public static class CategoryInfoBuilder {
         @CategoryInt
         private final int mCategory;
-        private boolean mHasMoreAction;
+        private boolean mHasFetchAction;
         private boolean mHasViewAllAction;
-        private boolean mHasReloadAction;
         private boolean mShowIfEmpty;
         private String mTitle = "";
         private String mNoSuggestionsMessage = "";
@@ -114,8 +113,8 @@
             mCategory = category;
         }
 
-        public CategoryInfoBuilder withMoreAction() {
-            mHasMoreAction = true;
+        public CategoryInfoBuilder withFetchAction() {
+            mHasFetchAction = true;
             return this;
         }
 
@@ -124,11 +123,6 @@
             return this;
         }
 
-        public CategoryInfoBuilder withReloadAction() {
-            mHasReloadAction = true;
-            return this;
-        }
-
         public CategoryInfoBuilder showIfEmpty() {
             mShowIfEmpty = true;
             return this;
@@ -151,8 +145,8 @@
         }
 
         public SuggestionsCategoryInfo build() {
-            return new SuggestionsCategoryInfo(mCategory, mTitle, mCardLayout, mHasMoreAction,
-                    mHasReloadAction, mHasViewAllAction, mShowIfEmpty, mNoSuggestionsMessage);
+            return new SuggestionsCategoryInfo(mCategory, mTitle, mCardLayout, mHasFetchAction,
+                    mHasViewAllAction, mShowIfEmpty, mNoSuggestionsMessage);
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
index 6cd872f..cc60922 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapterTest.java
@@ -680,30 +680,29 @@
         // 0   | Above-the-fold
         // 1   | Header
         // 2-4 | Sugg*3
-        // 5   | Footer
-        // 6   | Spacer
+        // 5   | Action
+        // 6   | Footer
+        // 7   | Spacer
 
         // Dismiss the second suggestion of the second section.
         mAdapter.dismissItem(3, itemDismissedCallback);
         verify(itemDismissedCallback).onResult(anyString());
         verify(dataObserver).onItemRangeRemoved(3, 1);
-        verify(dataObserver).onItemRangeChanged(5, 1, null);
+        verify(dataObserver).onItemRangeChanged(6, 1, null);
 
         // Make sure the call with the updated position works properly.
         mAdapter.dismissItem(3, itemDismissedCallback);
         verify(itemDismissedCallback, times(2)).onResult(anyString());
         verify(dataObserver, times(2)).onItemRangeRemoved(3, 1);
-        verify(dataObserver).onItemRangeChanged(4, 1, null);
+        verify(dataObserver).onItemRangeChanged(5, 1, null);
 
         // Dismiss the last suggestion in the section. We should now show the status card.
         reset(dataObserver);
         mAdapter.dismissItem(2, itemDismissedCallback);
         verify(itemDismissedCallback, times(3)).onResult(anyString());
         verify(dataObserver).onItemRangeRemoved(2, 1); // Suggestion removed
-        verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh
-        verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
         verify(dataObserver).onItemRangeChanged(4, 1, null); // Spacer refresh
-        verify(dataObserver).onItemRangeInserted(3, 1); // Action item added
+        verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
         verify(dataObserver).onItemRangeChanged(5, 1, null); // Spacer refresh
 
         // Adapter content:
@@ -723,9 +722,8 @@
                 createDummySuggestions(newSuggestionCount, KnownCategories.ARTICLES));
         verify(dataObserver).onItemRangeInserted(2, newSuggestionCount);
         verify(dataObserver).onItemRangeChanged(5 + newSuggestionCount, 1, null); // Spacer refresh
-        verify(dataObserver, times(2)).onItemRangeRemoved(2 + newSuggestionCount, 1);
+        verify(dataObserver).onItemRangeRemoved(2 + newSuggestionCount, 1);
         verify(dataObserver).onItemRangeChanged(4 + newSuggestionCount, 1, null); // Spacer refresh
-        verify(dataObserver).onItemRangeChanged(3 + newSuggestionCount, 1, null); // Spacer refresh
 
         // Adapter content:
         // Idx | Item
@@ -733,8 +731,9 @@
         // 0   | Above-the-fold
         // 1   | Header
         // 2-8 | Sugg*7
-        // 9   | Footer
-        // 10  | Spacer
+        // 9   | Action
+        // 10  | Footer
+        // 11  | Spacer
 
         reset(dataObserver);
         suggestionsSource.setSuggestionsForCategory(
@@ -742,10 +741,8 @@
         mAdapter.getSectionListForTesting().onCategoryStatusChanged(
                 KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT);
         verify(dataObserver).onItemRangeRemoved(2, newSuggestionCount);
-        verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh
-        verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
         verify(dataObserver).onItemRangeChanged(4, 1, null); // Spacer refresh
-        verify(dataObserver).onItemRangeInserted(3, 1); // Action item added
+        verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
         verify(dataObserver).onItemRangeChanged(5, 1, null); // Spacer refresh
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 3e10559..96db4e3 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -93,7 +93,7 @@
     @Feature({"Ntp"})
     public void testDismissSibling() {
         List<SnippetArticle> snippets = createDummySuggestions(3, TEST_CATEGORY_ID);
-        SuggestionsSection section = createSectionWithReloadAction(true);
+        SuggestionsSection section = createSectionWithFetchAction(true);
 
         section.setStatus(CategoryStatus.AVAILABLE);
         assertNotNull(section.getActionItemForTesting());
@@ -121,7 +121,7 @@
         List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
                 TEST_CATEGORY_ID);
 
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         // Simulate initialisation by the adapter. Here we don't care about the notifications, since
         // the RecyclerView will be updated through notifyDataSetChanged.
         section.setStatus(CategoryStatus.AVAILABLE);
@@ -141,7 +141,7 @@
         final int suggestionCount = 5;
         List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
                 TEST_CATEGORY_ID);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
 
         // Simulate initialisation by the adapter. Here we don't care about the notifications, since
         // the RecyclerView will be updated through notifyDataSetChanged.
@@ -168,7 +168,7 @@
     @Test
     @Feature({"Ntp"})
     public void testRemoveUnknownSuggestion() {
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setStatus(CategoryStatus.AVAILABLE);
         section.removeSuggestionById("foobar");
     }
@@ -180,7 +180,7 @@
         List<SnippetArticle> snippets = createDummySuggestions(suggestionCount,
                 TEST_CATEGORY_ID);
 
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setStatus(CategoryStatus.AVAILABLE);
         reset(mParent);
 
@@ -205,11 +205,7 @@
                 TEST_CATEGORY_ID);
 
         SuggestionsCategoryInfo info =
-                new CategoryInfoBuilder(TEST_CATEGORY_ID)
-                .withMoreAction()
-                .withReloadAction()
-                .showIfEmpty()
-                .build();
+                new CategoryInfoBuilder(TEST_CATEGORY_ID).withFetchAction().showIfEmpty().build();
         SuggestionsSection section = createSection(info);
         section.setStatus(CategoryStatus.AVAILABLE);
         reset(mParent);
@@ -232,7 +228,7 @@
     @Test
     @Feature({"Ntp"})
     public void testDismissSection() {
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setStatus(CategoryStatus.AVAILABLE);
         reset(mParent);
         assertEquals(2, section.getItemCount());
@@ -260,7 +256,7 @@
         mBridge.setIsOfflinePageModelLoaded(true);
         mBridge.setItems(Arrays.asList(item0, item1));
 
-        SuggestionsSection section = createSectionWithReloadAction(true);
+        SuggestionsSection section = createSectionWithFetchAction(true);
         section.setSuggestions(snippets, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         // Check that we pick up the correct information.
@@ -316,88 +312,29 @@
         // When all the actions are enabled, ViewAll always has the priority and is shown.
 
         // Spy so that VerifyAction can check methods being called.
-        SuggestionsCategoryInfo info =
-                spy(new CategoryInfoBuilder(TEST_CATEGORY_ID)
-                        .withMoreAction()
-                        .withReloadAction()
-                        .withViewAllAction()
-                        .showIfEmpty()
-                        .build());
+        SuggestionsCategoryInfo info = spy(new CategoryInfoBuilder(TEST_CATEGORY_ID)
+                                                   .withFetchAction()
+                                                   .withViewAllAction()
+                                                   .showIfEmpty()
+                                                   .build());
         SuggestionsSection section = createSection(info);
 
         assertTrue(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_VIEW_ALL);
-
-        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
-                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
-
-        assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_VIEW_ALL);
     }
 
     @Test
     @Feature({"Ntp"})
-    public void testReloadAndFetchMoreActionPriority() {
-        // When both Reload and FetchMore are enabled, FetchMore runs when we have suggestions, and
-        // Reload when we don't.
-
-        // Spy so that VerifyAction can check methods being called.
-        SuggestionsCategoryInfo info =
-                spy(new CategoryInfoBuilder(TEST_CATEGORY_ID)
-                        .withMoreAction()
-                        .withReloadAction()
-                        .showIfEmpty()
-                        .build());
-        SuggestionsSection section = createSection(info);
-
-        assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_RELOAD);
-
-        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
-                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
-
-        assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_FETCH_MORE);
-    }
-
-    @Test
-    @Feature({"Ntp"})
-    public void testReloadActionPriority() {
-        // When only Reload is enabled, it only shows when we have no suggestions.
+    public void testFetchActionPriority() {
+        // When only FetchMore is shown when enabled.
 
         // Spy so that VerifyAction can check methods being called.
         SuggestionsCategoryInfo info = spy(
-                new CategoryInfoBuilder(TEST_CATEGORY_ID).withReloadAction().showIfEmpty().build());
+                new CategoryInfoBuilder(TEST_CATEGORY_ID).withFetchAction().showIfEmpty().build());
         SuggestionsSection section = createSection(info);
 
         assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_RELOAD);
-
-        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
-                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
-
-        assertFalse(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_NONE);
-    }
-
-    @Test
-    @Feature({"Ntp"})
-    public void testFetchMoreActionPriority() {
-        // When only FetchMore is enabled, it only shows when we have suggestions.
-
-        // Spy so that VerifyAction can check methods being called.
-        SuggestionsCategoryInfo info = spy(
-                new CategoryInfoBuilder(TEST_CATEGORY_ID).withMoreAction().showIfEmpty().build());
-        SuggestionsSection section = createSection(info);
-
-        assertFalse(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_NONE);
-
-        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
-                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
-
-        assertTrue(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_FETCH_MORE);
+        verifyAction(section, ActionItem.ACTION_FETCH);
     }
 
     @Test
@@ -412,12 +349,6 @@
 
         assertFalse(section.getActionItemForTesting().isVisible());
         verifyAction(section, ActionItem.ACTION_NONE);
-
-        section.setSuggestions(createDummySuggestions(3, TEST_CATEGORY_ID),
-                CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
-
-        assertFalse(section.getActionItemForTesting().isVisible());
-        verifyAction(section, ActionItem.ACTION_NONE);
     }
 
     @Test
@@ -425,14 +356,14 @@
     public void testFetchMoreProgressDisplay() {
         final int suggestionCount = 3;
         SuggestionsCategoryInfo info = spy(
-                new CategoryInfoBuilder(TEST_CATEGORY_ID).withMoreAction().showIfEmpty().build());
+                new CategoryInfoBuilder(TEST_CATEGORY_ID).withFetchAction().showIfEmpty().build());
         SuggestionsSection section = createSection(info);
         section.setSuggestions(createDummySuggestions(suggestionCount, TEST_CATEGORY_ID),
                 CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         assertFalse(section.getProgressItemForTesting().isVisible());
 
         // Tap the button
-        verifyAction(section, ActionItem.ACTION_FETCH_MORE);
+        verifyAction(section, ActionItem.ACTION_FETCH);
         assertTrue(section.getProgressItemForTesting().isVisible());
 
         // Simulate receiving suggestions.
@@ -658,7 +589,7 @@
     @Feature({"Ntp"})
     public void testCardIsNotifiedWhenBecomingFirst() {
         List<SnippetArticle> suggestions = createDummySuggestions(5, /* categoryId = */ 42);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setSuggestions(suggestions, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         reset(mParent);
 
@@ -671,7 +602,7 @@
     @Feature({"Ntp"})
     public void testCardIsNotifiedWhenBecomingLast() {
         List<SnippetArticle> suggestions = createDummySuggestions(5, /* categoryId = */ 42);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setSuggestions(suggestions, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         reset(mParent);
 
@@ -684,7 +615,7 @@
     @Feature({"Ntp"})
     public void testCardIsNotifiedWhenBecomingSoleCard() {
         List<SnippetArticle> suggestions = createDummySuggestions(2, /* categoryId = */ 42);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setSuggestions(suggestions, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         reset(mParent);
 
@@ -697,7 +628,7 @@
     @Feature({"Ntp"})
     public void testGetItemDismissalGroupWithSuggestions() {
         List<SnippetArticle> suggestions = createDummySuggestions(5, TEST_CATEGORY_ID);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         section.setSuggestions(suggestions, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
         assertThat(section.getItemDismissalGroup(1).size(), is(1));
@@ -707,7 +638,7 @@
     @Test
     @Feature({"Ntp"})
     public void testGetItemDismissalGroupWithActionItem() {
-        SuggestionsSection section = createSectionWithReloadAction(true);
+        SuggestionsSection section = createSectionWithFetchAction(true);
         assertThat(section.getItemDismissalGroup(1).size(), is(2));
         assertThat(section.getItemDismissalGroup(1), contains(1, 2));
     }
@@ -715,7 +646,7 @@
     @Test
     @Feature({"Ntp"})
     public void testGetItemDismissalGroupWithoutActionItem() {
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
         assertThat(section.getItemDismissalGroup(1).size(), is(1));
         assertThat(section.getItemDismissalGroup(1), contains(1));
     }
@@ -724,7 +655,7 @@
     @Feature({"Ntp"})
     public void testCardIsNotifiedWhenNotTheLastAnymore() {
         List<SnippetArticle> suggestions = createDummySuggestions(5, /* categoryId = */ 42);
-        SuggestionsSection section = createSectionWithReloadAction(false);
+        SuggestionsSection section = createSectionWithFetchAction(false);
 
         section.setSuggestions(suggestions, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
         reset(mParent);
@@ -735,7 +666,7 @@
     }
 
     private SuggestionsSection createSectionWithSuggestions(List<SnippetArticle> snippets) {
-        SuggestionsSection section = createSectionWithReloadAction(true);
+        SuggestionsSection section = createSectionWithFetchAction(true);
         section.setStatus(CategoryStatus.AVAILABLE);
         section.setSuggestions(snippets, CategoryStatus.AVAILABLE, /* replaceExisting = */ true);
 
@@ -752,9 +683,9 @@
         return set;
     }
 
-    private SuggestionsSection createSectionWithReloadAction(boolean hasReloadAction) {
+    private SuggestionsSection createSectionWithFetchAction(boolean hasReloadAction) {
         CategoryInfoBuilder builder = new CategoryInfoBuilder(TEST_CATEGORY_ID).showIfEmpty();
-        if (hasReloadAction) builder.withReloadAction();
+        if (hasReloadAction) builder.withFetchAction();
         return createSection(builder.build());
     }
 
@@ -784,10 +715,7 @@
         verify(section.getCategoryInfo(),
                 (action == ActionItem.ACTION_VIEW_ALL ? times(1) : never()))
                 .performViewAllAction(navDelegate);
-        verify(suggestionsSource,
-                action == ActionItem.ACTION_RELOAD || action == ActionItem.ACTION_FETCH_MORE
-                        ? times(1)
-                        : never())
+        verify(suggestionsSource, (action == ActionItem.ACTION_FETCH ? times(1) : never()))
                 .fetchSuggestions(anyInt(), any(String[].class));
     }
 
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index a9c54355..396c6cd 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -977,10 +977,6 @@
   // Android doesn't use InitChromeLogging, so we close the log file manually.
   logging::CloseLogFile();
 #endif  // !defined(OS_ANDROID)
-
-#if defined(OS_WIN)
-  base::debug::RemoveHandleHooks();
-#endif
 }
 
 #if defined(OS_MACOSX)
diff --git a/chrome/browser/PRESUBMIT.py b/chrome/browser/PRESUBMIT.py
index 0dbbdae8..0350a06 100644
--- a/chrome/browser/PRESUBMIT.py
+++ b/chrome/browser/PRESUBMIT.py
@@ -20,6 +20,23 @@
 def CheckChangeOnCommit(input_api, output_api):
   return _CommonChecks(input_api, output_api)
 
+def _RunHistogramChecks(input_api, output_api, histogram_name):
+  try:
+    # Setup sys.path so that we can call histrogram code
+    import sys
+    original_sys_path = sys.path
+    sys.path = sys.path + [input_api.os_path.join(
+        input_api.change.RepositoryRoot(),
+        'tools', 'metrics', 'histograms')]
+
+    import presubmit_bad_message_reasons
+    return presubmit_bad_message_reasons.PrecheckBadMessage(input_api,
+      output_api, histogram_name)
+  except:
+    return [output_api.PresubmitError('Could not verify histogram!')]
+  finally:
+    sys.path = original_sys_path
+
 
 def _CommonChecks(input_api, output_api):
   """Checks common to both upload and commit."""
@@ -75,6 +92,8 @@
         input_api, output_api, file_filter=is_resource).RunChecks())
     results.extend(js_checker.JSChecker(
         input_api, output_api, file_filter=is_resource).RunChecks())
+    results.extend(_RunHistogramChecks(input_api, output_api,
+      "BadMessageReasonChrome"))
   finally:
     sys.path = old_path
 
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d9688de..4fd3c68 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1433,12 +1433,6 @@
     {"drop-sync-credential", IDS_FLAGS_DROP_SYNC_CREDENTIAL_NAME,
      IDS_FLAGS_DROP_SYNC_CREDENTIAL_DESCRIPTION, kOsAll,
      FEATURE_VALUE_TYPE(password_manager::features::kDropSyncCredential)},
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-    {"enable-extension-action-redesign",
-     IDS_FLAGS_EXTENSION_ACTION_REDESIGN_NAME,
-     IDS_FLAGS_EXTENSION_ACTION_REDESIGN_DESCRIPTION, kOsDesktop,
-     SINGLE_VALUE_TYPE(extensions::switches::kEnableExtensionActionRedesign)},
-#endif  // ENABLE_EXTENSIONS
 #if !defined(OS_ANDROID)
     {"enable-message-center-always-scroll-up-upon-notification-removal",
      IDS_FLAGS_MESSAGE_CENTER_ALWAYS_SCROLL_UP_UPON_REMOVAL_NAME,
diff --git a/chrome/browser/android/logo_bridge.cc b/chrome/browser/android/logo_bridge.cc
index 5bf6df3..b86e97c9 100644
--- a/chrome/browser/android/logo_bridge.cc
+++ b/chrome/browser/android/logo_bridge.cc
@@ -18,6 +18,7 @@
 #include "components/search_provider_logos/logo_tracker.h"
 #include "jni/LogoBridge_jni.h"
 #include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_status.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -173,11 +174,10 @@
 LogoBridge::LogoBridge(jobject j_profile)
     : logo_service_(nullptr), weak_ptr_factory_(this) {
   Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
-  if (profile) {
-    logo_service_ = LogoServiceFactory::GetForProfile(profile);
-    animated_logo_fetcher_ = base::MakeUnique<AnimatedLogoFetcher>(
-        profile->GetRequestContext());
-  }
+  DCHECK(profile);
+  logo_service_ = LogoServiceFactory::GetForProfile(profile);
+  animated_logo_fetcher_ = base::MakeUnique<AnimatedLogoFetcher>(
+      profile->GetRequestContext());
 }
 
 LogoBridge::~LogoBridge() {}
@@ -189,9 +189,6 @@
 void LogoBridge::GetCurrentLogo(JNIEnv* env,
                                 const JavaParamRef<jobject>& obj,
                                 const JavaParamRef<jobject>& j_logo_observer) {
-  if (!logo_service_)
-    return;
-
   // |observer| is deleted in LogoObserverAndroid::OnObserverRemoved().
   LogoObserverAndroid* observer = new LogoObserverAndroid(
       weak_ptr_factory_.GetWeakPtr(), env, j_logo_observer);
@@ -202,9 +199,6 @@
                                  const JavaParamRef<jobject>& obj,
                                  const JavaParamRef<jobject>& j_callback,
                                  const JavaParamRef<jstring>& j_url) {
-  if (!animated_logo_fetcher_)
-    return;
-
   GURL url = GURL(ConvertJavaStringToUTF8(env, j_url));
   animated_logo_fetcher_->Start(env, url, j_callback);
 }
diff --git a/chrome/browser/android/logo_bridge.h b/chrome/browser/android/logo_bridge.h
index 50a91ee..368a8c3 100644
--- a/chrome/browser/android/logo_bridge.h
+++ b/chrome/browser/android/logo_bridge.h
@@ -10,7 +10,6 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "net/url_request/url_fetcher_delegate.h"
 
 class LogoService;
 
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.cc b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
index c275945..3389af1 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.cc
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
@@ -202,9 +202,8 @@
   }
   return Java_SnippetsBridge_createSuggestionsCategoryInfo(
       env, j_category_id, ConvertUTF16ToJavaString(env, info->title()),
-      static_cast<int>(info->card_layout()), info->has_more_action(),
-      info->has_reload_action(), info->has_view_all_action(),
-      info->show_if_empty(),
+      static_cast<int>(info->card_layout()), info->has_fetch_action(),
+      info->has_view_all_action(), info->show_if_empty(),
       ConvertUTF16ToJavaString(env, info->no_suggestions_message()));
 }
 
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 49258430..eac0680 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -238,6 +238,8 @@
     "api/identity/identity_api.h",
     "api/identity/identity_constants.cc",
     "api/identity/identity_constants.h",
+    "api/identity/identity_get_profile_user_info_function.cc",
+    "api/identity/identity_get_profile_user_info_function.h",
     "api/identity/identity_launch_web_auth_flow_function.cc",
     "api/identity/identity_launch_web_auth_flow_function.h",
     "api/identity/identity_mint_queue.cc",
diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc
index 81d003ea3..f0ee2195 100644
--- a/chrome/browser/extensions/api/identity/identity_api.cc
+++ b/chrome/browser/extensions/api/identity/identity_api.cc
@@ -880,28 +880,4 @@
   return client_id;
 }
 
-IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
-}
-
-IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
-}
-
-ExtensionFunction::ResponseAction IdentityGetProfileUserInfoFunction::Run() {
-  if (GetProfile()->IsOffTheRecord()) {
-    return RespondNow(Error(identity_constants::kOffTheRecord));
-  }
-
-  AccountInfo account =
-      AccountTrackerServiceFactory::GetForProfile(GetProfile())
-          ->GetAccountInfo(GetPrimaryAccountId(GetProfile()));
-  api::identity::ProfileUserInfo profile_user_info;
-  if (extension()->permissions_data()->HasAPIPermission(
-          APIPermission::kIdentityEmail)) {
-    profile_user_info.email = account.email;
-    profile_user_info.id = account.gaia;
-  }
-
-  return RespondNow(OneArgument(profile_user_info.ToValue()));
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h
index b39ae41..66599fc 100644
--- a/chrome/browser/extensions/api/identity/identity_api.h
+++ b/chrome/browser/extensions/api/identity/identity_api.h
@@ -18,6 +18,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/identity/extension_token_key.h"
 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
+#include "chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h"
 #include "chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.h"
 #include "chrome/browser/extensions/api/identity/identity_mint_queue.h"
 #include "chrome/browser/extensions/api/identity/identity_remove_cached_auth_token_function.h"
@@ -291,21 +292,6 @@
   std::unique_ptr<IdentitySigninFlow> signin_flow_;
 };
 
-class IdentityGetProfileUserInfoFunction
-    : public ChromeUIThreadExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("identity.getProfileUserInfo",
-                             IDENTITY_GETPROFILEUSERINFO);
-
-  IdentityGetProfileUserInfoFunction();
-
- private:
-  ~IdentityGetProfileUserInfoFunction() override;
-
-  // UIThreadExtensionFunction implementation.
-  ExtensionFunction::ResponseAction Run() override;
-};
-
 }  // namespace extensions
 
 #endif  // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_API_H_
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index 8948254..a652c4b 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -23,6 +23,7 @@
 #endif
 #include "chrome/browser/extensions/api/identity/identity_api.h"
 #include "chrome/browser/extensions/api/identity/identity_constants.h"
+#include "chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h"
 #include "chrome/browser/extensions/api/identity/identity_launch_web_auth_flow_function.h"
 #include "chrome/browser/extensions/api/identity/identity_remove_cached_auth_token_function.h"
 #include "chrome/browser/extensions/component_loader.h"
diff --git a/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc b/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc
new file mode 100644
index 0000000..09405866
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.cc
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h"
+
+#include "chrome/browser/extensions/api/identity/identity_constants.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/common/extensions/api/identity.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+
+IdentityGetProfileUserInfoFunction::IdentityGetProfileUserInfoFunction() {
+}
+
+IdentityGetProfileUserInfoFunction::~IdentityGetProfileUserInfoFunction() {
+}
+
+ExtensionFunction::ResponseAction IdentityGetProfileUserInfoFunction::Run() {
+  if (GetProfile()->IsOffTheRecord()) {
+    return RespondNow(Error(identity_constants::kOffTheRecord));
+  }
+
+  AccountInfo account = SigninManagerFactory::GetForProfile(
+      GetProfile())->GetAuthenticatedAccountInfo();
+  api::identity::ProfileUserInfo profile_user_info;
+  if (extension()->permissions_data()->HasAPIPermission(
+          APIPermission::kIdentityEmail)) {
+    profile_user_info.email = account.email;
+    profile_user_info.id = account.gaia;
+  }
+
+  return RespondNow(OneArgument(profile_user_info.ToValue()));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h b/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h
new file mode 100644
index 0000000..2d3b434
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/identity_get_profile_user_info_function.h
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_PROFILE_USER_INFO_FUNCTION_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_PROFILE_USER_INFO_FUNCTION_H_
+
+#include "chrome/browser/extensions/chrome_extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+
+namespace extensions {
+
+class IdentityGetProfileUserInfoFunction
+    : public ChromeUIThreadExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("identity.getProfileUserInfo",
+                             IDENTITY_GETPROFILEUSERINFO);
+
+  IdentityGetProfileUserInfoFunction();
+
+ private:
+  ~IdentityGetProfileUserInfoFunction() override;
+
+  // UIThreadExtensionFunction implementation.
+  ExtensionFunction::ResponseAction Run() override;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_PROFILE_USER_INFO_FUNCTION_H_
diff --git a/chrome/browser/extensions/dev_mode_bubble_delegate.cc b/chrome/browser/extensions/dev_mode_bubble_delegate.cc
index e838351e..d23784e 100644
--- a/chrome/browser/extensions/dev_mode_bubble_delegate.cc
+++ b/chrome/browser/extensions/dev_mode_bubble_delegate.cc
@@ -74,6 +74,10 @@
   return false;
 }
 
+bool DevModeBubbleDelegate::ShouldAcknowledgeOnDeactivate() const {
+  return false;
+}
+
 bool DevModeBubbleDelegate::ShouldShowExtensionList() const {
   return false;
 }
diff --git a/chrome/browser/extensions/dev_mode_bubble_delegate.h b/chrome/browser/extensions/dev_mode_bubble_delegate.h
index a79a2a0..2f34d52 100644
--- a/chrome/browser/extensions/dev_mode_bubble_delegate.h
+++ b/chrome/browser/extensions/dev_mode_bubble_delegate.h
@@ -37,6 +37,7 @@
   base::string16 GetActionButtonLabel() const override;
   base::string16 GetDismissButtonLabel() const override;
   bool ShouldCloseOnDeactivate() const override;
+  bool ShouldAcknowledgeOnDeactivate() const override;
   bool ShouldShowExtensionList() const override;
   bool ShouldHighlightExtensions() const override;
   bool ShouldLimitToEnabledExtensions() const override;
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index fdf670b64..e3590c1 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -30,8 +30,8 @@
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_registry.h"
@@ -399,11 +399,13 @@
   return handled;
 }
 
-void ExtensionActionRunner::DidNavigateMainFrame(
-    const content::LoadCommittedDetails& details,
-    const content::FrameNavigateParams& params) {
-  if (details.is_in_page)
+void ExtensionActionRunner::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame() ||
+      !navigation_handle->HasCommitted() ||
+      navigation_handle->IsSamePage()) {
     return;
+  }
 
   LogUMA();
   num_page_requests_ = 0;
diff --git a/chrome/browser/extensions/extension_action_runner.h b/chrome/browser/extensions/extension_action_runner.h
index 16e90970..a7ab6e3e 100644
--- a/chrome/browser/extensions/extension_action_runner.h
+++ b/chrome/browser/extensions/extension_action_runner.h
@@ -164,9 +164,8 @@
   // content::WebContentsObserver implementation.
   bool OnMessageReceived(const IPC::Message& message,
                          content::RenderFrameHost* render_frame_host) override;
-  void DidNavigateMainFrame(
-      const content::LoadCommittedDetails& details,
-      const content::FrameNavigateParams& params) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
 
   // ExtensionRegistryObserver:
   void OnExtensionUnloaded(content::BrowserContext* browser_context,
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.cc b/chrome/browser/extensions/extension_message_bubble_controller.cc
index 15ea349a..96fda8c 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller.cc
@@ -316,7 +316,8 @@
   // If the bubble was closed due to deactivation, don't treat it as
   // acknowledgment so that the user will see the bubble again (until they
   // explicitly take an action).
-  if (user_action_ != ACTION_DISMISS_DEACTIVATION) {
+  if (user_action_ != ACTION_DISMISS_DEACTIVATION ||
+      delegate_->ShouldAcknowledgeOnDeactivate()) {
     AcknowledgeExtensions();
     if (delegate_->ClearProfileSetAfterAction())
       GetProfileSet()->clear();
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.h b/chrome/browser/extensions/extension_message_bubble_controller.h
index 0bd63a5..53a13a7 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.h
+++ b/chrome/browser/extensions/extension_message_bubble_controller.h
@@ -68,6 +68,10 @@
     // Returns true if the bubble should close when the widget deactivates.
     virtual bool ShouldCloseOnDeactivate() const = 0;
 
+    // Returns true if the bubble should be considered acknowledged when the
+    // widget deactivates.
+    virtual bool ShouldAcknowledgeOnDeactivate() const = 0;
+
     // Whether to show a list of extensions in the bubble.
     virtual bool ShouldShowExtensionList() const = 0;
 
diff --git a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
index 90db931..c599ff74 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/dev_mode_bubble_delegate.h"
@@ -28,6 +29,7 @@
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model_factory.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
@@ -359,7 +361,25 @@
   DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleTest);
 };
 
-TEST_F(ExtensionMessageBubbleTest, BubbleReshowsOnDeactivationDismissal) {
+class ExtensionMessageBubbleTestWithParam
+    : public ExtensionMessageBubbleTest,
+      public ::testing::WithParamInterface<bool> {};
+
+// Test that the bubble correctly treats dismissal due to deactivation.
+// Currently, the NTP bubble is the only one that has flexible behavior (toggled
+// by a feature).
+TEST_P(ExtensionMessageBubbleTestWithParam,
+       BubbleCorrectlyReshowsOnDeactivationDismissal) {
+  const bool kAcknowledgeOnDeactivate = GetParam();
+  base::test::ScopedFeatureList feature_list;
+  if (kAcknowledgeOnDeactivate) {
+    feature_list.InitAndEnableFeature(
+        features::kAcknowledgeNtpOverrideOnDeactivate);
+  } else {
+    feature_list.InitAndDisableFeature(
+        features::kAcknowledgeNtpOverrideOnDeactivate);
+  }
+
   Init();
 
   ASSERT_TRUE(LoadExtensionOverridingNtp("1", kId1, Manifest::INTERNAL));
@@ -392,39 +412,54 @@
   // No extension should have become disabled.
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
   EXPECT_TRUE(registry->enabled_extensions().GetByID(kId2));
-  // And since it was dismissed due to deactivation, the extension should not
-  // have been acknowledged.
-  EXPECT_FALSE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId2));
 
-  bubble.set_action_on_show(
-      FakeExtensionMessageBubble::BUBBLE_ACTION_DISMISS_DEACTIVATION);
-  controller.reset(new TestExtensionMessageBubbleController(
-      new NtpOverriddenBubbleDelegate(browser()->profile()), browser()));
-  controller->SetIsActiveBubble();
-  // The bubble shouldn't show again for the same profile (we don't want to
-  // be annoying).
-  EXPECT_FALSE(controller->ShouldShow());
-  controller->ClearProfileListForTesting();
-  EXPECT_TRUE(controller->ShouldShow());
-  // Explicitly click the dismiss button. The extension should be acknowledged.
-  bubble.set_controller(controller.get());
-  bubble.set_action_on_show(
-      FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
-  bubble.Show();
-  EXPECT_TRUE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId2));
+  if (kAcknowledgeOnDeactivate) {
+    EXPECT_TRUE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId2));
+
+    controller.reset(new TestExtensionMessageBubbleController(
+        new ProxyOverriddenBubbleDelegate(browser()->profile()), browser()));
+    controller->ClearProfileListForTesting();
+    controller->SetIsActiveBubble();
+    EXPECT_FALSE(controller->ShouldShow());
+  } else {
+    // Since the bubble was dismissed due to deactivation, the extension should
+    // not have been acknowledged.
+    EXPECT_FALSE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId2));
+
+    bubble.set_action_on_show(
+        FakeExtensionMessageBubble::BUBBLE_ACTION_DISMISS_DEACTIVATION);
+    controller.reset(new TestExtensionMessageBubbleController(
+        new NtpOverriddenBubbleDelegate(browser()->profile()), browser()));
+    controller->SetIsActiveBubble();
+    // The bubble shouldn't show again for the same profile (we don't want to
+    // be annoying).
+    EXPECT_FALSE(controller->ShouldShow());
+    controller->ClearProfileListForTesting();
+    EXPECT_TRUE(controller->ShouldShow());
+    // Explicitly click the dismiss button. The extension should be
+    // acknowledged.
+    bubble.set_controller(controller.get());
+    bubble.set_action_on_show(
+        FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
+    bubble.Show();
+    EXPECT_TRUE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId2));
+  }
 
   // Uninstall the current ntp-controlling extension, allowing the other to
   // take control.
   service_->UninstallExtension(kId2, UNINSTALL_REASON_FOR_TESTING,
                                base::Bind(&base::DoNothing), nullptr);
 
-  // Even though we already showed for the given profile, we should show again,
-  // because it's a different extension.
+  // Even though we already showed for the given profile, we should show
+  // again, because it's a different extension.
   controller.reset(new TestExtensionMessageBubbleController(
       new NtpOverriddenBubbleDelegate(browser()->profile()), browser()));
   EXPECT_TRUE(controller->ShouldShow());
 }
 
+INSTANTIATE_TEST_CASE_P(ExtensionMessageBubbleTest,
+                        ExtensionMessageBubbleTestWithParam, testing::Bool());
+
 // The feature this is meant to test is only enacted on Windows, but it should
 // pass on all platforms.
 TEST_F(ExtensionMessageBubbleTest, WipeoutControllerTest) {
diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc
index 70d4111..4f3cf1b 100644
--- a/chrome/browser/extensions/installed_loader.cc
+++ b/chrome/browser/extensions/installed_loader.cc
@@ -355,6 +355,7 @@
   int file_access_allowed_count = 0;
   int file_access_not_allowed_count = 0;
   int eventless_event_pages_count = 0;
+  int off_store_item_count = 0;
 
   const ExtensionSet& extensions = extension_registry_->enabled_extensions();
   for (ExtensionSet::const_iterator iter = extensions.begin();
@@ -527,6 +528,9 @@
           ++file_access_not_allowed_count;
       }
     }
+
+    if (!ManifestURL::UpdatesFromGallery(extension))
+      ++off_store_item_count;
   }
 
   const ExtensionSet& disabled_extensions =
@@ -615,6 +619,8 @@
                            extension_prefs_->GetCorruptedDisableCount());
   UMA_HISTOGRAM_COUNTS_100("Extensions.EventlessEventPages",
                            eventless_event_pages_count);
+  UMA_HISTOGRAM_COUNTS_100("Extensions.LoadOffStoreItems",
+                           off_store_item_count);
 }
 
 int InstalledLoader::GetCreationFlags(const ExtensionInfo* info) {
diff --git a/chrome/browser/extensions/ntp_overridden_bubble_delegate.cc b/chrome/browser/extensions/ntp_overridden_bubble_delegate.cc
index 5e96f0a..177dcae 100644
--- a/chrome/browser/extensions/ntp_overridden_bubble_delegate.cc
+++ b/chrome/browser/extensions/ntp_overridden_bubble_delegate.cc
@@ -4,11 +4,13 @@
 
 #include "chrome/browser/extensions/ntp_overridden_bubble_delegate.h"
 
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_web_ui.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "extensions/browser/extension_registry.h"
@@ -105,6 +107,11 @@
   return true;
 }
 
+bool NtpOverriddenBubbleDelegate::ShouldAcknowledgeOnDeactivate() const {
+  return base::FeatureList::IsEnabled(
+      features::kAcknowledgeNtpOverrideOnDeactivate);
+}
+
 bool NtpOverriddenBubbleDelegate::ShouldShowExtensionList() const {
   return false;
 }
diff --git a/chrome/browser/extensions/ntp_overridden_bubble_delegate.h b/chrome/browser/extensions/ntp_overridden_bubble_delegate.h
index 1845c79..15d0911 100644
--- a/chrome/browser/extensions/ntp_overridden_bubble_delegate.h
+++ b/chrome/browser/extensions/ntp_overridden_bubble_delegate.h
@@ -36,6 +36,7 @@
   base::string16 GetActionButtonLabel() const override;
   base::string16 GetDismissButtonLabel() const override;
   bool ShouldCloseOnDeactivate() const override;
+  bool ShouldAcknowledgeOnDeactivate() const override;
   bool ShouldShowExtensionList() const override;
   bool ShouldHighlightExtensions() const override;
   bool ShouldLimitToEnabledExtensions() const override;
diff --git a/chrome/browser/extensions/proxy_overridden_bubble_delegate.cc b/chrome/browser/extensions/proxy_overridden_bubble_delegate.cc
index afb2e0f7..1d05de2 100644
--- a/chrome/browser/extensions/proxy_overridden_bubble_delegate.cc
+++ b/chrome/browser/extensions/proxy_overridden_bubble_delegate.cc
@@ -120,6 +120,10 @@
   return false;
 }
 
+bool ProxyOverriddenBubbleDelegate::ShouldAcknowledgeOnDeactivate() const {
+  return false;
+}
+
 bool ProxyOverriddenBubbleDelegate::ShouldShowExtensionList() const {
   return false;
 }
diff --git a/chrome/browser/extensions/proxy_overridden_bubble_delegate.h b/chrome/browser/extensions/proxy_overridden_bubble_delegate.h
index da36b2e0..7c254d65 100644
--- a/chrome/browser/extensions/proxy_overridden_bubble_delegate.h
+++ b/chrome/browser/extensions/proxy_overridden_bubble_delegate.h
@@ -35,6 +35,7 @@
   base::string16 GetActionButtonLabel() const override;
   base::string16 GetDismissButtonLabel() const override;
   bool ShouldCloseOnDeactivate() const override;
+  bool ShouldAcknowledgeOnDeactivate() const override;
   bool ShouldShowExtensionList() const override;
   bool ShouldHighlightExtensions() const override;
   bool ShouldLimitToEnabledExtensions() const override;
diff --git a/chrome/browser/extensions/settings_api_bubble_delegate.cc b/chrome/browser/extensions/settings_api_bubble_delegate.cc
index c60ce7f..8fb398e 100644
--- a/chrome/browser/extensions/settings_api_bubble_delegate.cc
+++ b/chrome/browser/extensions/settings_api_bubble_delegate.cc
@@ -199,6 +199,10 @@
   return type_ != BUBBLE_TYPE_STARTUP_PAGES;
 }
 
+bool SettingsApiBubbleDelegate::ShouldAcknowledgeOnDeactivate() const {
+  return false;
+}
+
 bool SettingsApiBubbleDelegate::ShouldShowExtensionList() const {
   return false;
 }
diff --git a/chrome/browser/extensions/settings_api_bubble_delegate.h b/chrome/browser/extensions/settings_api_bubble_delegate.h
index 737fa58..9e36bd46 100644
--- a/chrome/browser/extensions/settings_api_bubble_delegate.h
+++ b/chrome/browser/extensions/settings_api_bubble_delegate.h
@@ -36,6 +36,7 @@
   base::string16 GetActionButtonLabel() const override;
   base::string16 GetDismissButtonLabel() const override;
   bool ShouldCloseOnDeactivate() const override;
+  bool ShouldAcknowledgeOnDeactivate() const override;
   bool ShouldShowExtensionList() const override;
   bool ShouldHighlightExtensions() const override;
   bool ShouldLimitToEnabledExtensions() const override;
diff --git a/chrome/browser/extensions/suspicious_extension_bubble_delegate.cc b/chrome/browser/extensions/suspicious_extension_bubble_delegate.cc
index 931fc750..504c937 100644
--- a/chrome/browser/extensions/suspicious_extension_bubble_delegate.cc
+++ b/chrome/browser/extensions/suspicious_extension_bubble_delegate.cc
@@ -104,6 +104,10 @@
   return false;
 }
 
+bool SuspiciousExtensionBubbleDelegate::ShouldAcknowledgeOnDeactivate() const {
+  return false;
+}
+
 bool SuspiciousExtensionBubbleDelegate::ShouldShowExtensionList() const {
   return true;
 }
diff --git a/chrome/browser/extensions/suspicious_extension_bubble_delegate.h b/chrome/browser/extensions/suspicious_extension_bubble_delegate.h
index d36f500..016e0e38 100644
--- a/chrome/browser/extensions/suspicious_extension_bubble_delegate.h
+++ b/chrome/browser/extensions/suspicious_extension_bubble_delegate.h
@@ -33,6 +33,7 @@
   base::string16 GetActionButtonLabel() const override;
   base::string16 GetDismissButtonLabel() const override;
   bool ShouldCloseOnDeactivate() const override;
+  bool ShouldAcknowledgeOnDeactivate() const override;
   bool ShouldShowExtensionList() const override;
   bool ShouldHighlightExtensions() const override;
   bool ShouldLimitToEnabledExtensions() const override;
diff --git a/chrome/browser/ntp_snippets/download_suggestions_provider.cc b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
index 9eb137b..2b43b4eb 100644
--- a/chrome/browser/ntp_snippets/download_suggestions_provider.cc
+++ b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
@@ -188,8 +188,7 @@
   return CategoryInfo(
       l10n_util::GetStringUTF16(IDS_NTP_DOWNLOAD_SUGGESTIONS_SECTION_HEADER),
       ntp_snippets::ContentSuggestionsCardLayout::MINIMAL_CARD,
-      /*has_more_action=*/false,
-      /*has_reload_action=*/false,
+      /*has_fetch_action=*/false,
       /*has_view_all_action=*/true,
       /*show_if_empty=*/false,
       l10n_util::GetStringUTF16(IDS_NTP_DOWNLOADS_SUGGESTIONS_SECTION_EMPTY));
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 9c577fa..141e2aa5 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -64,6 +64,7 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/thumbnails/thumbnail_service_factory.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service_factory.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/tabs/pinned_tab_service_factory.h"
@@ -223,6 +224,9 @@
 #endif
   NotifierStateTrackerFactory::GetInstance();
   data_use_measurement::ChromeDataUseAscriberServiceFactory::GetInstance();
+#if !defined(OS_ANDROID)
+  SMSServiceFactory::GetInstance();
+#endif
   dom_distiller::DomDistillerServiceFactory::GetInstance();
   domain_reliability::DomainReliabilityServiceFactory::GetInstance();
   DownloadServiceFactory::GetInstance();
diff --git a/chrome/browser/sync/test/integration/sync_errors_test.cc b/chrome/browser/sync/test/integration/sync_errors_test.cc
index cce9867..961af28 100644
--- a/chrome/browser/sync/test/integration/sync_errors_test.cc
+++ b/chrome/browser/sync/test/integration/sync_errors_test.cc
@@ -202,7 +202,13 @@
 
 // Tests that on receiving CLIENT_DATA_OBSOLETE sync engine gets restarted and
 // initialized with different cache_guld.
-IN_PROC_BROWSER_TEST_F(SyncErrorTest, ClientDataObsoleteTest) {
+// Flaky on Windows and Linux. See crbug.com/683216
+#if defined(OS_WIN) || defined(OS_LINUX)
+#define MAYBE_ClientDataObsoleteTest DISABLED_ClientDataObsoleteTest
+#else
+#define MAYBE_ClientDataObsoleteTest ClientDataObsoleteTest
+#endif
+IN_PROC_BROWSER_TEST_F(SyncErrorTest, MAYBE_ClientDataObsoleteTest) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   const BookmarkNode* node1 = AddFolder(0, 0, "title1");
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 712efa6..35f819a6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -717,6 +717,10 @@
       "desktop_ios_promotion/desktop_ios_promotion_controller.h",
       "desktop_ios_promotion/desktop_ios_promotion_util.cc",
       "desktop_ios_promotion/desktop_ios_promotion_util.h",
+      "desktop_ios_promotion/sms_service.cc",
+      "desktop_ios_promotion/sms_service.h",
+      "desktop_ios_promotion/sms_service_factory.cc",
+      "desktop_ios_promotion/sms_service_factory.h",
       "exclusive_access/exclusive_access_bubble.cc",
       "exclusive_access/exclusive_access_bubble.h",
       "exclusive_access/exclusive_access_bubble_type.cc",
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service.cc b/chrome/browser/ui/desktop_ios_promotion/sms_service.cc
new file mode 100644
index 0000000..0a79da9
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service.cc
@@ -0,0 +1,314 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
+#include "base/strings/stringprintf.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/base/load_flags.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace {
+
+const char kDesktopIOSPromotionOAuthScope[] =
+    "https://www.googleapis.com/auth/mobile_user_preferences";
+
+const char kDesktopIOSPromotionQueryPhoneNumber[] =
+    "https://growth-pa.googleapis.com/v1/get_verified_phone_numbers";
+
+const char kDesktopIOSPromotionSendSMS[] =
+    "https://growth-pa.googleapis.com/v1/send_sms";
+
+const char kPostDataMimeType[] = "application/json";
+
+const char kSendSMSPromoFormat[] = "{promo_id:%s}";
+
+// The maximum number of retries for the URLFetcher requests.
+const size_t kMaxRetries = 1;
+
+class RequestImpl : public SMSService::Request,
+                    private OAuth2TokenService::Consumer,
+                    private net::URLFetcherDelegate {
+ public:
+  ~RequestImpl() override {}
+
+  // Returns the response code received from the server, which will only be
+  // valid if the request succeeded.
+  int GetResponseCode() override { return response_code_; }
+
+  // Returns the contents of the response body received from the server.
+  const std::string& GetResponseBody() override { return response_body_; }
+
+  bool IsPending() override { return is_pending_; }
+
+ private:
+  friend class ::SMSService;
+
+  RequestImpl(
+      OAuth2TokenService* token_service,
+      SigninManagerBase* signin_manager,
+      const scoped_refptr<net::URLRequestContextGetter>& request_context,
+      const GURL& url,
+      const SMSService::CompletionCallback& callback)
+      : OAuth2TokenService::Consumer("desktop_ios_promotion"),
+        token_service_(token_service),
+        signin_manager_(signin_manager),
+        request_context_(request_context),
+        url_(url),
+        post_data_mime_type_(kPostDataMimeType),
+        response_code_(0),
+        auth_retry_count_(0),
+        callback_(callback),
+        is_pending_(false) {
+    DCHECK(token_service_);
+    DCHECK(signin_manager_);
+    DCHECK(request_context_);
+  }
+
+  void Start() override {
+    OAuth2TokenService::ScopeSet oauth_scopes;
+    oauth_scopes.insert(kDesktopIOSPromotionOAuthScope);
+    token_request_ = token_service_->StartRequest(
+        signin_manager_->GetAuthenticatedAccountId(), oauth_scopes, this);
+    is_pending_ = true;
+  }
+
+  // content::URLFetcherDelegate interface.
+  void OnURLFetchComplete(const net::URLFetcher* source) override {
+    DCHECK_EQ(source, url_fetcher_.get());
+    response_code_ = url_fetcher_->GetResponseCode();
+
+    UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+        "DesktopIOSPromotion.OAuthTokenResponseCode",
+        net::HttpUtil::MapStatusCodeForHistogram(response_code_),
+        net::HttpUtil::GetStatusCodesForHistogram());
+
+    // If the response code indicates that the token might not be valid,
+    // invalidate the token and try again.
+    if (response_code_ == net::HTTP_UNAUTHORIZED && ++auth_retry_count_ <= 1) {
+      OAuth2TokenService::ScopeSet oauth_scopes;
+      oauth_scopes.insert(kDesktopIOSPromotionOAuthScope);
+      token_service_->InvalidateAccessToken(
+          signin_manager_->GetAuthenticatedAccountId(), oauth_scopes,
+          access_token_);
+
+      access_token_.clear();
+      Start();
+      return;
+    }
+    url_fetcher_->GetResponseAsString(&response_body_);
+    url_fetcher_.reset();
+    is_pending_ = false;
+    callback_.Run(this, response_code_ == net::HTTP_OK);
+    // It is valid for the callback to delete |this|, so do not access any
+    // members below here.
+  }
+
+  // OAuth2TokenService::Consumer interface.
+  void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+                         const std::string& access_token,
+                         const base::Time& expiration_time) override {
+    token_request_.reset();
+    DCHECK(!access_token.empty());
+    access_token_ = access_token;
+
+    UMA_HISTOGRAM_BOOLEAN("DesktopIOSPromotion.OAuthTokenCompletion", true);
+
+    // Got an access token -- start the actual API request.
+    url_fetcher_ = CreateUrlFetcher(access_token);
+    url_fetcher_->Start();
+  }
+
+  void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+                         const GoogleServiceAuthError& error) override {
+    token_request_.reset();
+    is_pending_ = false;
+
+    UMA_HISTOGRAM_BOOLEAN("DesktopIOSPromotion.OAuthTokenCompletion", false);
+
+    callback_.Run(this, false);
+    // It is valid for the callback to delete |this|, so do not access any
+    // members below here.
+  }
+
+  // Helper for creating a new URLFetcher for the API request.
+  std::unique_ptr<net::URLFetcher> CreateUrlFetcher(
+      const std::string& access_token) {
+    net::URLFetcher::RequestType request_type =
+        post_data_ ? net::URLFetcher::POST : net::URLFetcher::GET;
+    std::unique_ptr<net::URLFetcher> fetcher =
+        net::URLFetcher::Create(url_, request_type, this);
+    fetcher->SetRequestContext(request_context_.get());
+    fetcher->SetMaxRetriesOn5xx(kMaxRetries);
+    fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+                          net::LOAD_DO_NOT_SAVE_COOKIES);
+    fetcher->AddExtraRequestHeader("Authorization: Bearer " + access_token);
+    fetcher->AddExtraRequestHeader(
+        "X-Developer-Key: " +
+        GaiaUrls::GetInstance()->oauth2_chrome_client_id());
+
+    if (post_data_)
+      fetcher->SetUploadData(post_data_mime_type_, post_data_.value());
+    return fetcher;
+  }
+
+  void SetPostData(const std::string& post_data) override {
+    SetPostDataAndType(post_data, kPostDataMimeType);
+  }
+
+  void SetPostDataAndType(const std::string& post_data,
+                          const std::string& mime_type) override {
+    post_data_ = post_data;
+    post_data_mime_type_ = mime_type;
+  }
+
+  OAuth2TokenService* token_service_;
+  SigninManagerBase* signin_manager_;
+  scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+  // The URL of the API endpoint.
+  GURL url_;
+
+  // POST data to be sent with the request (may be empty).
+  base::Optional<std::string> post_data_;
+
+  // MIME type of the post requests. Defaults to text/plain.
+  std::string post_data_mime_type_;
+
+  // The OAuth2 access token request.
+  std::unique_ptr<OAuth2TokenService::Request> token_request_;
+
+  // The current OAuth2 access token.
+  std::string access_token_;
+
+  // Handles the actual API requests after the OAuth token is acquired.
+  std::unique_ptr<net::URLFetcher> url_fetcher_;
+
+  // Holds the response code received from the server.
+  int response_code_;
+
+  // Holds the response body received from the server.
+  std::string response_body_;
+
+  // The number of times this request has already been retried due to
+  // authorization problems.
+  int auth_retry_count_;
+
+  // The callback to execute when the query is complete.
+  SMSService::CompletionCallback callback_;
+
+  // True if the request was started and has not yet completed, otherwise false.
+  bool is_pending_;
+};
+
+}  // namespace
+
+SMSService::Request::Request() {}
+
+SMSService::Request::~Request() {}
+
+SMSService::SMSService(
+    OAuth2TokenService* token_service,
+    SigninManagerBase* signin_manager,
+    const scoped_refptr<net::URLRequestContextGetter>& request_context)
+    : token_service_(token_service),
+      signin_manager_(signin_manager),
+      request_context_(request_context),
+      weak_ptr_factory_(this) {}
+
+SMSService::~SMSService() {}
+
+SMSService::Request* SMSService::CreateRequest(
+    const GURL& url,
+    const CompletionCallback& callback) {
+  return new RequestImpl(token_service_, signin_manager_, request_context_, url,
+                         callback);
+}
+
+void SMSService::QueryPhoneNumber(const PhoneNumberCallback& callback) {
+  CompletionCallback completion_callback =
+      base::Bind(&SMSService::QueryPhoneNumberCompletionCallback,
+                 weak_ptr_factory_.GetWeakPtr(), callback);
+
+  GURL url(kDesktopIOSPromotionQueryPhoneNumber);
+  Request* request = CreateRequest(url, completion_callback);
+  // This placeholder is required by the API.
+  request->SetPostData("{}");
+  pending_requests_[request] = base::WrapUnique(request);
+  request->Start();
+}
+
+void SMSService::SendSMS(const std::string& promo_id,
+                         const SMSService::PhoneNumberCallback& callback) {
+  CompletionCallback completion_callback = base::Bind(
+      &SMSService::SendSMSCallback, weak_ptr_factory_.GetWeakPtr(), callback);
+  GURL url(kDesktopIOSPromotionSendSMS);
+  Request* request = CreateRequest(url, completion_callback);
+  request->SetPostData(
+      base::StringPrintf(kSendSMSPromoFormat, promo_id.c_str()));
+  pending_requests_[request] = base::WrapUnique(request);
+  request->Start();
+}
+
+void SMSService::QueryPhoneNumberCompletionCallback(
+    const SMSService::PhoneNumberCallback& callback,
+    SMSService::Request* request,
+    bool success) {
+  std::unique_ptr<Request> request_ptr = std::move(pending_requests_[request]);
+  pending_requests_.erase(request);
+
+  std::string phone_number;
+  bool has_number = false;
+  std::unique_ptr<base::Value> value =
+      base::JSONReader::Read(request->GetResponseBody());
+  if (value.get() && value.get()->IsType(base::Value::Type::DICTIONARY)) {
+    const base::DictionaryValue* dictionary;
+    if (value->GetAsDictionary(&dictionary)) {
+      const base::ListValue* number_list;
+      if (dictionary->GetList("phoneNumber", &number_list)) {
+        const base::DictionaryValue* sub_dictionary;
+        // For now only handle the first number.
+        if (number_list->GetSize() > 0 &&
+            number_list->GetDictionary(0, &sub_dictionary)) {
+          if (sub_dictionary->GetString("phoneNumber", &phone_number))
+            has_number = true;
+        }
+      }
+    }
+  }
+  callback.Run(request, success && has_number, phone_number);
+}
+
+void SMSService::SendSMSCallback(
+    const SMSService::PhoneNumberCallback& callback,
+    SMSService::Request* request,
+    bool success) {
+  std::unique_ptr<Request> request_ptr = std::move(pending_requests_[request]);
+  pending_requests_.erase(request);
+
+  std::string phone_number;
+  bool has_number = false;
+  std::unique_ptr<base::Value> value =
+      base::JSONReader::Read(request->GetResponseBody());
+  if (value.get() && value.get()->IsType(base::Value::Type::DICTIONARY)) {
+    const base::DictionaryValue* dictionary;
+    if (value->GetAsDictionary(&dictionary)) {
+      if (dictionary->GetString("phoneNumber", &phone_number))
+        has_number = true;
+    }
+  }
+  callback.Run(request, success && has_number, phone_number);
+}
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service.h b/chrome/browser/ui/desktop_ios_promotion/sms_service.h
new file mode 100644
index 0000000..6fb7e09
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service.h
@@ -0,0 +1,109 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_H_
+#define CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+class OAuth2TokenService;
+class SigninManagerBase;
+
+// Provides an API for querying a logged in users's verified phone number,
+// and sending a predetermined promotional SMS to that number.  This class is
+// based heavily on WebHistoryService's implementation to query Google services.
+class SMSService : public KeyedService {
+ public:
+  class Request {
+   public:
+    virtual ~Request();
+
+    virtual bool IsPending() = 0;
+
+    // Returns the response code received from the server, which will only be
+    // valid if the request succeeded.
+    virtual int GetResponseCode() = 0;
+
+    // Returns the contents of the response body received from the server.
+    virtual const std::string& GetResponseBody() = 0;
+
+    virtual void SetPostData(const std::string& post_data) = 0;
+
+    virtual void SetPostDataAndType(const std::string& post_data,
+                                    const std::string& mime_type) = 0;
+
+    // Tells the request to begin.
+    virtual void Start() = 0;
+
+   protected:
+    Request();
+  };
+
+  typedef base::Callback<
+      void(Request*, bool success, const std::string& number)>
+      PhoneNumberCallback;
+  typedef base::Callback<void(Request*, bool success)> CompletionCallback;
+
+  SMSService(
+      OAuth2TokenService* token_service,
+      SigninManagerBase* signin_manager,
+      const scoped_refptr<net::URLRequestContextGetter>& request_context);
+  ~SMSService() override;
+
+  // Query the logged in user's verified phone number.
+  void QueryPhoneNumber(const PhoneNumberCallback& callback);
+
+  // Send an SMS to the logged in user's verified phone number.  The text of
+  // the SMS is determined by |promo_id|.
+  void SendSMS(const std::string& promo_id,
+               const SMSService::PhoneNumberCallback& callback);
+
+ protected:
+  void QueryPhoneNumberCompletionCallback(
+      const SMSService::PhoneNumberCallback& callback,
+      SMSService::Request* request,
+      bool success);
+
+  void SendSMSCallback(const SMSService::PhoneNumberCallback& callback,
+                       SMSService::Request* request,
+                       bool success);
+
+ private:
+  virtual Request* CreateRequest(const GURL& url,
+                                 const CompletionCallback& callback);
+
+  // Stores pointer to OAuth2TokenService and SigninManagerBase instance. They
+  // must outlive the SMSService and can be null during
+  // tests.
+  OAuth2TokenService* token_service_;
+  SigninManagerBase* signin_manager_;
+
+  // Request context getter to use.
+  scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+  // Pending expiration requests to be canceled if not complete by profile
+  // shutdown.
+  std::map<Request*, std::unique_ptr<Request>> pending_requests_;
+
+  base::WeakPtrFactory<SMSService> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SMSService);
+};
+
+#endif  // CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_H_
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.cc b/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.cc
new file mode 100644
index 0000000..536c117e
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.cc
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service_factory.h"
+
+#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service.h"
+#include "components/browser_sync/profile_sync_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "net/url_request/url_request_context_getter.h"
+
+// static
+SMSServiceFactory* SMSServiceFactory::GetInstance() {
+  return base::Singleton<SMSServiceFactory>::get();
+}
+
+// static
+SMSService* SMSServiceFactory::GetForProfile(Profile* profile) {
+  return static_cast<SMSService*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+KeyedService* SMSServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  return new SMSService(
+      ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
+      SigninManagerFactory::GetForProfile(profile),
+      profile->GetRequestContext());
+}
+
+SMSServiceFactory::SMSServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "SMSServiceFactory",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
+  DependsOn(SigninManagerFactory::GetInstance());
+}
+
+SMSServiceFactory::~SMSServiceFactory() {}
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.h b/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.h
new file mode 100644
index 0000000..8d7b1f7e
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service_factory.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class SMSService;
+
+class SMSServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  // Get the singleton instance of the factory.
+  static SMSServiceFactory* GetInstance();
+
+  // Get the SMSService for |profile|, creating one if needed.
+  static SMSService* GetForProfile(Profile* profile);
+
+ protected:
+  // Overridden from BrowserContextKeyedServiceFactory.
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<SMSServiceFactory>;
+
+  SMSServiceFactory();
+  ~SMSServiceFactory() override;
+
+  DISALLOW_COPY_AND_ASSIGN(SMSServiceFactory);
+};
+
+#endif  // CHROME_BROWSER_UI_DESKTOP_IOS_PROMOTION_SMS_SERVICE_FACTORY_H_
diff --git a/chrome/browser/ui/desktop_ios_promotion/sms_service_unittest.cc b/chrome/browser/ui/desktop_ios_promotion/sms_service_unittest.cc
new file mode 100644
index 0000000..ffe3a88
--- /dev/null
+++ b/chrome/browser/ui/desktop_ios_promotion/sms_service_unittest.cc
@@ -0,0 +1,199 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/desktop_ios_promotion/sms_service.h"
+
+#include "base/run_loop.h"
+#include "components/signin/core/browser/account_tracker_service.h"
+#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
+#include "components/signin/core/browser/fake_signin_manager.h"
+#include "components/signin/core/browser/test_signin_client.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// A testing web history service that does extra checks and creates a
+// TestRequest instead of a normal request.
+class TestingSMSService : public SMSService {
+ public:
+  explicit TestingSMSService(
+      ProfileOAuth2TokenService* token_service,
+      SigninManagerBase* signin_manager,
+      const scoped_refptr<net::URLRequestContextGetter>& request_context)
+      : SMSService(token_service, signin_manager, request_context) {}
+
+  ~TestingSMSService() override {}
+
+  SMSService::Request* CreateRequest(
+      const GURL& url,
+      const CompletionCallback& callback) override;
+
+  void SetNextRequestResponseData(int response_code,
+                                  const std::string& response_body) {
+    next_response_code_ = response_code;
+    next_response_body_ = response_body;
+  }
+
+  void QueryPhoneNumberCallbackSucceeded(SMSService::Request* request,
+                                         bool success,
+                                         const std::string& number) {
+    EXPECT_TRUE(success);
+    EXPECT_EQ(number, "1");
+  }
+
+  void QueryPhoneNumberCallbackFailed(SMSService::Request* request,
+                                      bool success,
+                                      const std::string& number) {
+    EXPECT_FALSE(success);
+    EXPECT_EQ(number, "");
+  }
+
+ private:
+  int next_response_code_;
+  std::string next_response_body_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestingSMSService);
+};
+
+// A testing request class that allows expected values to be filled in.
+class TestRequest : public SMSService::Request {
+ public:
+  TestRequest(const SMSService::CompletionCallback& callback,
+              int response_code,
+              const std::string& response_body)
+      : callback_(callback),
+        response_code_(response_code),
+        response_body_(response_body),
+        is_pending_(false) {}
+
+  ~TestRequest() override {}
+
+  // history::Request overrides
+  bool IsPending() override { return is_pending_; }
+  int GetResponseCode() override { return response_code_; }
+  const std::string& GetResponseBody() override { return response_body_; }
+  void SetPostData(const std::string& post_data) override {
+    post_data_ = post_data;
+  }
+  void SetPostDataAndType(const std::string& post_data,
+                          const std::string& mime_type) override {
+    SetPostData(post_data);
+  }
+  void Start() override {
+    is_pending_ = true;
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::Bind(&TestRequest::MimicReturnFromFetch, base::Unretained(this)));
+  }
+
+  void MimicReturnFromFetch() {
+    callback_.Run(this, response_code_ == net::HTTP_OK);
+  }
+
+ private:
+  GURL url_;
+  SMSService::CompletionCallback callback_;
+  int response_code_;
+  std::string response_body_;
+  std::string post_data_;
+  bool is_pending_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestRequest);
+};
+
+SMSService::Request* TestingSMSService::CreateRequest(
+    const GURL& url,
+    const CompletionCallback& callback) {
+  SMSService::Request* request =
+      new TestRequest(callback, next_response_code_, next_response_body_);
+  return request;
+}
+
+}  // namespace
+
+// A test class used for testing the SMSService class.
+// In order for SMSService to be valid, we must have a valid
+// ProfileSyncService. Using the ProfileSyncServiceMock class allows to
+// assign specific return values as needed to make sure the web history
+// service is available.
+class SMSServiceTest : public testing::Test {
+ public:
+  SMSServiceTest()
+      : signin_client_(nullptr),
+        signin_manager_(&signin_client_, &account_tracker_),
+        url_request_context_(new net::TestURLRequestContextGetter(
+            base::ThreadTaskRunnerHandle::Get())),
+        sms_service_(&token_service_, &signin_manager_, url_request_context_) {}
+
+  ~SMSServiceTest() override {}
+
+  void TearDown() override {
+    base::RunLoop run_loop;
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  TestingSMSService* sms_service() { return &sms_service_; }
+
+  void SetNextRequestResponseData(int response_code,
+                                  const std::string& response_body) {
+    sms_service_.SetNextRequestResponseData(response_code, response_body);
+  }
+
+ private:
+  base::MessageLoop message_loop_;
+  FakeProfileOAuth2TokenService token_service_;
+  AccountTrackerService account_tracker_;
+  TestSigninClient signin_client_;
+  FakeSigninManagerBase signin_manager_;
+  scoped_refptr<net::URLRequestContextGetter> url_request_context_;
+  TestingSMSService sms_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(SMSServiceTest);
+};
+
+TEST_F(SMSServiceTest, VerifyJsonData) {
+  // Test that properly formatted response with good response code returns true
+  // as expected.
+  std::string query_phone_valid =
+      "{\n\"phoneNumber\": "
+      "  [\n"
+      "    {\"phoneNumber\": \"1\"},"
+      "    {\"phoneNumber\": \"2\"}"
+      "  ]\n"
+      "}";
+  sms_service()->SetNextRequestResponseData(net::HTTP_OK, query_phone_valid);
+  sms_service()->QueryPhoneNumber(
+      base::Bind(&TestingSMSService::QueryPhoneNumberCallbackSucceeded,
+                 base::Unretained(sms_service())));
+  base::RunLoop().RunUntilIdle();
+  std::string send_sms_valid = "{\"phoneNumber\": \"1\"}";
+  sms_service()->SetNextRequestResponseData(net::HTTP_OK, send_sms_valid);
+  std::string promo_id = "";
+  sms_service()->SendSMS(
+      promo_id,
+      base::Bind(&TestingSMSService::QueryPhoneNumberCallbackSucceeded,
+                 base::Unretained(sms_service())));
+  base::RunLoop().RunUntilIdle();
+  // Test that improperly formatted response returns no number.
+  std::string query_phone_invalid =
+      "{\n\"phoneNumber\": "
+      "  [\n"
+      "  ]\n"
+      "}";
+  sms_service()->SetNextRequestResponseData(net::HTTP_OK, query_phone_invalid);
+  sms_service()->QueryPhoneNumber(
+      base::Bind(&TestingSMSService::QueryPhoneNumberCallbackFailed,
+                 base::Unretained(sms_service())));
+  base::RunLoop().RunUntilIdle();
+  std::string send_sms_invalid = "{}";
+  sms_service()->SetNextRequestResponseData(net::HTTP_OK, send_sms_invalid);
+  sms_service()->SendSMS(
+      promo_id, base::Bind(&TestingSMSService::QueryPhoneNumberCallbackFailed,
+                           base::Unretained(sms_service())));
+  base::RunLoop().RunUntilIdle();
+}
diff --git a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
index 9a372f34..63820ad 100644
--- a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
@@ -124,8 +124,16 @@
   MediaRouterActionController* action_controller_ = nullptr;
 };
 
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN)
+// Flaky on chromeos, linux, win: https://crbug.com/658005
+#define MAYBE_OpenDialogWithMediaRouterAction \
+        DISABLED_OpenDialogWithMediaRouterAction
+#else
+#define MAYBE_OpenDialogWithMediaRouterAction OpenDialogWithMediaRouterAction
+#endif
+
 IN_PROC_BROWSER_TEST_F(MediaRouterUIBrowserTest,
-                       OpenDialogWithMediaRouterAction) {
+                       MAYBE_OpenDialogWithMediaRouterAction) {
   // We start off at about:blank page.
   // Make sure there is 1 tab and media router is enabled.
   ASSERT_EQ(1, browser()->tab_strip_model()->count());
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 6f26ffd..b8359a4 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -126,6 +126,11 @@
 // Enabled or disabled the Material Design version of chrome://extensions.
 const base::Feature kMaterialDesignExtensions{
     "MaterialDesignExtensions", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Sets whether dismissing the new-tab-page override bubble counts as
+// acknowledgement.
+extern const base::Feature kAcknowledgeNtpOverrideOnDeactivate{
+    "AcknowledgeNtpOverrideOnDeactivate", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
 // Enables or disables the Material Design version of chrome://history.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index d78d6c7..14748db 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -78,6 +78,7 @@
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 extern const base::Feature kMaterialDesignExtensions;
+extern const base::Feature kAcknowledgeNtpOverrideOnDeactivate;
 #endif
 
 extern const base::Feature kMaterialDesignHistory;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 59eddeb..b953ff92 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3766,6 +3766,7 @@
       "../browser/ui/autofill/country_combobox_model_unittest.cc",
       "../browser/ui/autofill/save_card_bubble_controller_impl_unittest.cc",
       "../browser/ui/bluetooth/bluetooth_chooser_controller_unittest.cc",
+      "../browser/ui/desktop_ios_promotion/sms_service_unittest.cc",
       "../browser/ui/passwords/manage_passwords_ui_controller_unittest.cc",
     ]
     deps += [
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 3f2e6e657..d43dc48f 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-9236.0.0
\ No newline at end of file
+9240.0.0
\ No newline at end of file
diff --git a/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java
index 0a0d77a..73a688b 100644
--- a/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java
+++ b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java
@@ -45,8 +45,9 @@
     private CronetOutputStream mOutputStream;
     private UrlResponseInfo mResponseInfo;
     private CronetException mException;
-    private boolean mOnRedirectCalled = false;
-    private boolean mHasResponse = false;
+    private boolean mOnRedirectCalled;
+    // Whether response headers are received, the request is failed, or the request is canceled.
+    private boolean mHasResponseHeadersOrCompleted;
     private List<Map.Entry<String, String>> mResponseHeadersList;
     private Map<String, List<String>> mResponseHeadersMap;
 
@@ -437,6 +438,7 @@
         @Override
         public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
             mResponseInfo = info;
+            mHasResponseHeadersOrCompleted = true;
             // Quits the message loop since we have the headers now.
             mMessageLoop.quit();
         }
@@ -508,7 +510,7 @@
             if (mOutputStream != null) {
                 mOutputStream.setRequestCompleted(exception);
             }
-            mHasResponse = true;
+            mHasResponseHeadersOrCompleted = true;
             mMessageLoop.quit();
         }
     }
@@ -525,13 +527,12 @@
                 mOutputStream.close();
             }
         }
-        if (!mHasResponse) {
+        if (!mHasResponseHeadersOrCompleted) {
             startRequest();
             // Blocks until onResponseStarted or onFailed is called.
             mMessageLoop.loop();
-            mHasResponse = true;
         }
-        checkHasResponse();
+        checkHasResponseHeaders();
     }
 
     /**
@@ -539,8 +540,8 @@
      * an exception occurred before headers received. This method should only be
      * called after onResponseStarted or onFailed.
      */
-    private void checkHasResponse() throws IOException {
-        if (!mHasResponse) throw new IllegalStateException("No response.");
+    private void checkHasResponseHeaders() throws IOException {
+        if (!mHasResponseHeadersOrCompleted) throw new IllegalStateException("No response.");
         if (mException != null) {
             throw mException;
         } else if (mResponseInfo == null) {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/urlconnection/CronetFixedModeOutputStreamTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/urlconnection/CronetFixedModeOutputStreamTest.java
index ea4e624..87cdd62 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/urlconnection/CronetFixedModeOutputStreamTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/urlconnection/CronetFixedModeOutputStreamTest.java
@@ -68,6 +68,23 @@
     @SmallTest
     @Feature({"Cronet"})
     @CompareDefaultWithCronet
+    // Regression test for crbug.com/687600.
+    public void testZeroLengthWriteWithNoResponseBody() throws Exception {
+        URL url = new URL(NativeTestServer.getEchoBodyURL());
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setDoOutput(true);
+        connection.setRequestMethod("POST");
+        connection.setFixedLengthStreamingMode(0);
+        OutputStream out = connection.getOutputStream();
+        out.write(new byte[] {});
+        assertEquals(200, connection.getResponseCode());
+        assertEquals("OK", connection.getResponseMessage());
+        connection.disconnect();
+    }
+
+    @SmallTest
+    @Feature({"Cronet"})
+    @CompareDefaultWithCronet
     public void testWriteAfterRequestFailed() throws Exception {
         URL url = new URL(NativeTestServer.getEchoBodyURL());
         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
diff --git a/components/dom_distiller/core/dom_distiller_service.cc b/components/dom_distiller/core/dom_distiller_service.cc
index 78534bf..bb31c6a0 100644
--- a/components/dom_distiller/core/dom_distiller_service.cc
+++ b/components/dom_distiller/core/dom_distiller_service.cc
@@ -55,6 +55,9 @@
 }
 
 syncer::SyncableService* DomDistillerService::GetSyncableService() const {
+  if (!store_) {
+    return nullptr;
+  }
   return store_->GetSyncableService();
 }
 
@@ -75,7 +78,7 @@
     std::unique_ptr<DistillerPage> distiller_page,
     const ArticleAvailableCallback& article_cb) {
   ArticleEntry entry;
-  const bool is_already_added = store_->GetEntryByUrl(url, &entry);
+  const bool is_already_added = store_ && store_->GetEntryByUrl(url, &entry);
 
   TaskTracker* task_tracker = nullptr;
   if (is_already_added) {
@@ -112,18 +115,21 @@
 }
 
 bool DomDistillerService::HasEntry(const std::string& entry_id) {
-  return store_->GetEntryById(entry_id, NULL);
+  return store_ && store_->GetEntryById(entry_id, NULL);
 }
 
 std::string DomDistillerService::GetUrlForEntry(const std::string& entry_id) {
   ArticleEntry entry;
-  if (store_->GetEntryById(entry_id, &entry)) {
+  if (store_ && store_->GetEntryById(entry_id, &entry)) {
     return entry.pages().Get(0).url();
   }
   return "";
 }
 
 std::vector<ArticleEntry> DomDistillerService::GetEntries() const {
+  if (!store_) {
+    return std::vector<ArticleEntry>();
+  }
   return store_->GetEntries();
 }
 
@@ -136,7 +142,7 @@
     task_tracker->CancelSaveCallbacks();
   }
 
-  if (!store_->GetEntryById(entry_id, entry.get())) {
+  if (!store_ || !store_->GetEntryById(entry_id, entry.get())) {
     return std::unique_ptr<ArticleEntry>();
   }
 
@@ -151,7 +157,7 @@
     std::unique_ptr<DistillerPage> distiller_page,
     const std::string& entry_id) {
   ArticleEntry entry;
-  if (!store_->GetEntryById(entry_id, &entry)) {
+  if (!store_ || !store_->GetEntryById(entry_id, &entry)) {
     return std::unique_ptr<ViewerHandle>();
   }
 
@@ -194,7 +200,7 @@
     const GURL& url,
     TaskTracker** task_tracker) {
   ArticleEntry entry;
-  if (store_->GetEntryByUrl(url, &entry)) {
+  if (store_ && store_->GetEntryByUrl(url, &entry)) {
     return GetOrCreateTaskTrackerForEntry(entry, task_tracker);
   }
 
@@ -261,7 +267,7 @@
     const DistilledArticleProto* article_proto,
     bool distillation_succeeded) {
   DCHECK(IsEntryValid(entry));
-  if (distillation_succeeded) {
+  if (store_ && distillation_succeeded) {
     DCHECK(article_proto);
     DCHECK_GT(article_proto->pages_size(), 0);
     store_->AddEntry(entry);
@@ -271,12 +277,16 @@
 
 void DomDistillerService::AddObserver(DomDistillerObserver* observer) {
   DCHECK(observer);
-  store_->AddObserver(observer);
+  if (store_) {
+    store_->AddObserver(observer);
+  }
 }
 
 void DomDistillerService::RemoveObserver(DomDistillerObserver* observer) {
   DCHECK(observer);
-  store_->RemoveObserver(observer);
+  if (store_) {
+    store_->RemoveObserver(observer);
+  }
 }
 
 DistilledPagePrefs* DomDistillerService::GetDistilledPagePrefs() {
diff --git a/components/exo/pointer.cc b/components/exo/pointer.cc
index 9ca231e5..31b4299 100644
--- a/components/exo/pointer.cc
+++ b/components/exo/pointer.cc
@@ -74,6 +74,9 @@
   if (!focus_)
     return;
 
+  // This is used to avoid unnecessary cursor changes.
+  bool cursor_changed = false;
+
   // If surface is different than the current pointer surface then remove the
   // current surface and add the new surface.
   if (surface != surface_) {
@@ -100,10 +103,18 @@
           ->GetContainer(ash::kShellWindowId_MouseCursorContainer)
           ->AddChild(surface_->window());
     }
+    cursor_changed = true;
   }
 
   // Update hotspot.
-  hotspot_ = hotspot;
+  if (hotspot != hotspot_) {
+    hotspot_ = hotspot;
+    cursor_changed = true;
+  }
+
+  // Early out if cursor did not change.
+  if (!cursor_changed)
+    return;
 
   // If |surface_| is set then ascynchrounsly capture a snapshot of cursor,
   // otherwise cancel pending capture and immediately set the cursor to "none".
@@ -225,7 +236,7 @@
 void Pointer::OnCursorSetChanged(ui::CursorSetType cursor_set) {
   // Capture new cursor in case UI scale changed.
   if (focus_ && surface_)
-    UpdateCursor();
+    CaptureCursor();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/components/guest_view/renderer/guest_view_container.cc b/components/guest_view/renderer/guest_view_container.cc
index 4d5c341..890fb36 100644
--- a/components/guest_view/renderer/guest_view_container.cc
+++ b/components/guest_view/renderer/guest_view_container.cc
@@ -4,6 +4,7 @@
 
 #include "components/guest_view/renderer/guest_view_container.h"
 
+#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/guest_view/common/guest_view_constants.h"
diff --git a/components/nacl/browser/PRESUBMIT.py b/components/nacl/browser/PRESUBMIT.py
new file mode 100644
index 0000000..b2cac48
--- /dev/null
+++ b/components/nacl/browser/PRESUBMIT.py
@@ -0,0 +1,27 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromium presubmit script to check that BadMessage enums in histograms.xml
+match the corresponding bad_message.h file.
+"""
+
+def _RunHistogramChecks(input_api, output_api, histogram_name):
+  try:
+    # Setup sys.path so that we can call histrogram code
+    import sys
+    original_sys_path = sys.path
+    sys.path = sys.path + [input_api.os_path.join(
+      input_api.change.RepositoryRoot(),
+      'tools', 'metrics', 'histograms')]
+
+    import presubmit_bad_message_reasons
+    return presubmit_bad_message_reasons.PrecheckBadMessage(input_api,
+      output_api, histogram_name)
+  except:
+    return [output_api.PresubmitError('Could not verify histogram!')]
+  finally:
+    sys.path = original_sys_path
+
+def CheckChangeOnUpload(input_api, output_api):
+  return _RunHistogramChecks(input_api, output_api, "BadMessageReasonNaCl")
diff --git a/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc b/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
index 0b452a0e..8a6e5b8 100644
--- a/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
+++ b/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
@@ -110,8 +110,7 @@
   return CategoryInfo(
       l10n_util::GetStringUTF16(IDS_NTP_BOOKMARK_SUGGESTIONS_SECTION_HEADER),
       ContentSuggestionsCardLayout::MINIMAL_CARD,
-      /*has_more_action=*/false,
-      /*has_reload_action=*/false,
+      /*has_fetch_action=*/false,
       /*has_view_all_action=*/true,
       /*show_if_empty=*/false,
       l10n_util::GetStringUTF16(IDS_NTP_BOOKMARK_SUGGESTIONS_SECTION_EMPTY));
diff --git a/components/ntp_snippets/category_info.cc b/components/ntp_snippets/category_info.cc
index e3d3058d..e8b32ea 100644
--- a/components/ntp_snippets/category_info.cc
+++ b/components/ntp_snippets/category_info.cc
@@ -8,15 +8,13 @@
 
 CategoryInfo::CategoryInfo(const base::string16& title,
                            ContentSuggestionsCardLayout card_layout,
-                           bool has_more_action,
-                           bool has_reload_action,
+                           bool has_fetch_action,
                            bool has_view_all_action,
                            bool show_if_empty,
                            const base::string16& no_suggestions_message)
     : title_(title),
       card_layout_(card_layout),
-      has_more_action_(has_more_action),
-      has_reload_action_(has_reload_action),
+      has_fetch_action_(has_fetch_action),
       has_view_all_action_(has_view_all_action),
       show_if_empty_(show_if_empty),
       no_suggestions_message_(no_suggestions_message) {}
diff --git a/components/ntp_snippets/category_info.h b/components/ntp_snippets/category_info.h
index dadf287..a85e867 100644
--- a/components/ntp_snippets/category_info.h
+++ b/components/ntp_snippets/category_info.h
@@ -25,8 +25,7 @@
  public:
   CategoryInfo(const base::string16& title,
                ContentSuggestionsCardLayout card_layout,
-               bool has_more_action,
-               bool has_reload_action,
+               bool has_fetch_action,
                bool has_view_all_action,
                bool show_if_empty,
                const base::string16& no_suggestions_message);
@@ -43,13 +42,9 @@
   // Layout of the cards to be used to display suggestions in this category.
   ContentSuggestionsCardLayout card_layout() const { return card_layout_; }
 
-  // Whether the category supports a "More" action, that triggers fetching more
-  // suggestions for the category, while keeping the current ones.
-  bool has_more_action() const { return has_more_action_; }
-
-  // Whether the category supports a "Reload" action, that triggers fetching new
-  // suggestions to replace the current ones.
-  bool has_reload_action() const { return has_reload_action_; }
+  // Whether the category supports a "Fetch" action, that triggers fetching more
+  // suggestions for the category.
+  bool has_fetch_action() const { return has_fetch_action_; }
 
   // Whether the category supports a "ViewAll" action, that triggers displaying
   // all the content related to the current categories.
@@ -70,8 +65,7 @@
   ContentSuggestionsCardLayout card_layout_;
 
   // Supported actions for the category.
-  bool has_more_action_;
-  bool has_reload_action_;
+  bool has_fetch_action_;
   bool has_view_all_action_;
 
   // Whether to show the category if a fetch returns no suggestions.
diff --git a/components/ntp_snippets/content_suggestions_service_unittest.cc b/components/ntp_snippets/content_suggestions_service_unittest.cc
index 29ddca3..0ada9202 100644
--- a/components/ntp_snippets/content_suggestions_service_unittest.cc
+++ b/components/ntp_snippets/content_suggestions_service_unittest.cc
@@ -433,8 +433,7 @@
   const CategoryInfo& actual = result.value();
   EXPECT_THAT(expected.title(), Eq(actual.title()));
   EXPECT_THAT(expected.card_layout(), Eq(actual.card_layout()));
-  EXPECT_THAT(expected.has_more_action(), Eq(actual.has_more_action()));
-  EXPECT_THAT(expected.has_reload_action(), Eq(actual.has_reload_action()));
+  EXPECT_THAT(expected.has_fetch_action(), Eq(actual.has_fetch_action()));
   EXPECT_THAT(expected.has_view_all_action(), Eq(actual.has_view_all_action()));
 }
 
diff --git a/components/ntp_snippets/mock_content_suggestions_provider.cc b/components/ntp_snippets/mock_content_suggestions_provider.cc
index 02c99b6..007ecb16 100644
--- a/components/ntp_snippets/mock_content_suggestions_provider.cc
+++ b/components/ntp_snippets/mock_content_suggestions_provider.cc
@@ -37,8 +37,10 @@
 CategoryInfo MockContentSuggestionsProvider::GetCategoryInfo(
     Category category) {
   return CategoryInfo(base::ASCIIToUTF16("Section title"),
-                      ContentSuggestionsCardLayout::FULL_CARD, true, false,
-                      true, false,
+                      ContentSuggestionsCardLayout::FULL_CARD,
+                      /*has_fetch_action=*/true,
+                      /*has_view_all_action=*/true,
+                      /*show_if_empty=*/false,
                       base::ASCIIToUTF16("No suggestions message"));
 }
 
diff --git a/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc b/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
index f8056d0f..7ef2d6e 100644
--- a/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
+++ b/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
@@ -111,8 +111,7 @@
   return CategoryInfo(
       l10n_util::GetStringUTF16(IDS_NTP_RECENT_TAB_SUGGESTIONS_SECTION_HEADER),
       ContentSuggestionsCardLayout::MINIMAL_CARD,
-      /*has_more_action=*/false,
-      /*has_reload_action=*/false,
+      /*has_fetch_action=*/false,
       /*has_view_all_action=*/false,
       /*show_if_empty=*/false,
       l10n_util::GetStringUTF16(IDS_NTP_RECENT_TAB_SUGGESTIONS_SECTION_EMPTY));
diff --git a/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider_unittest.cc b/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider_unittest.cc
index 64acc93..bc3d6d2 100644
--- a/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider_unittest.cc
+++ b/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider_unittest.cc
@@ -177,9 +177,7 @@
 
 TEST_F(RecentTabSuggestionsProviderTest, ShouldDeliverCorrectCategoryInfo) {
   EXPECT_FALSE(
-      provider()->GetCategoryInfo(recent_tabs_category()).has_more_action());
-  EXPECT_FALSE(
-      provider()->GetCategoryInfo(recent_tabs_category()).has_reload_action());
+      provider()->GetCategoryInfo(recent_tabs_category()).has_fetch_action());
   EXPECT_FALSE(provider()
                    ->GetCategoryInfo(recent_tabs_category())
                    .has_view_all_action());
diff --git a/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc b/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
index 27cfb98..7e73cdd 100644
--- a/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
+++ b/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
@@ -117,8 +117,7 @@
   return CategoryInfo(l10n_util::GetStringUTF16(
                           IDS_NTP_PHYSICAL_WEB_PAGE_SUGGESTIONS_SECTION_HEADER),
                       ContentSuggestionsCardLayout::FULL_CARD,
-                      /*has_more_action=*/false,
-                      /*has_reload_action=*/false,
+                      /*has_fetch_action=*/false,
                       /*has_view_all_action=*/false,
                       /*show_if_empty=*/false,
                       l10n_util::GetStringUTF16(
diff --git a/components/ntp_snippets/remote/json_request.cc b/components/ntp_snippets/remote/json_request.cc
index 8e522936..ec52caa 100644
--- a/components/ntp_snippets/remote/json_request.cc
+++ b/components/ntp_snippets/remote/json_request.cc
@@ -139,10 +139,7 @@
                         : l10n_util::GetStringUTF16(
                               IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER),
       ContentSuggestionsCardLayout::FULL_CARD,
-      // TODO(dgn): merge has_more_action and has_reload_action when we remove
-      // the kFetchMoreFeature flag. See https://crbug.com/667752
-      /*has_more_action=*/true,
-      /*has_reload_action=*/true,
+      /*has_fetch_action=*/true,
       /*has_view_all_action=*/false,
       /*show_if_empty=*/true,
       l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_EMPTY));
@@ -152,10 +149,7 @@
                                      bool allow_fetching_more_results) {
   return CategoryInfo(
       title, ContentSuggestionsCardLayout::FULL_CARD,
-      // TODO(dgn): merge has_more_action and has_reload_action when we remove
-      // the kFetchMoreFeature flag. See https://crbug.com/667752
-      /*has_more_action=*/allow_fetching_more_results,
-      /*has_reload_action=*/allow_fetching_more_results,
+      /*has_fetch_action=*/allow_fetching_more_results,
       /*has_view_all_action=*/false,
       /*show_if_empty=*/false,
       // TODO(tschumann): The message for no-articles is likely wrong
diff --git a/components/ntp_snippets/remote/remote_suggestions_fetcher.cc b/components/ntp_snippets/remote/remote_suggestions_fetcher.cc
index 56eaa12..80fa8c3 100644
--- a/components/ntp_snippets/remote/remote_suggestions_fetcher.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_fetcher.cc
@@ -211,10 +211,7 @@
                         : l10n_util::GetStringUTF16(
                               IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER),
       ContentSuggestionsCardLayout::FULL_CARD,
-      // TODO(dgn): merge has_more_action and has_reload_action when we remove
-      // the kFetchMoreFeature flag. See https://crbug.com/667752
-      /*has_more_action=*/true,
-      /*has_reload_action=*/true,
+      /*has_fetch_action=*/true,
       /*has_view_all_action=*/false,
       /*show_if_empty=*/true,
       l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_EMPTY));
@@ -224,10 +221,7 @@
                                      bool allow_fetching_more_results) {
   return CategoryInfo(
       title, ContentSuggestionsCardLayout::FULL_CARD,
-      // TODO(dgn): merge has_more_action and has_reload_action when we remove
-      // the kFetchMoreFeature flag. See https://crbug.com/667752
-      /*has_more_action=*/allow_fetching_more_results,
-      /*has_reload_action=*/allow_fetching_more_results,
+      /*has_fetch_action=*/allow_fetching_more_results,
       /*has_view_all_action=*/false,
       /*show_if_empty=*/false,
       // TODO(tschumann): The message for no-articles is likely wrong
diff --git a/components/ntp_snippets/remote/remote_suggestions_fetcher_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_fetcher_unittest.cc
index 86b4bb1..14ac6b1 100644
--- a/components/ntp_snippets/remote/remote_suggestions_fetcher_unittest.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_fetcher_unittest.cc
@@ -115,12 +115,8 @@
 }
 
 MATCHER(IsCategoryInfoForArticles, "") {
-  if (!arg.has_more_action()) {
-    *result_listener << "missing expected has_more_action";
-    return false;
-  }
-  if (!arg.has_reload_action()) {
-    *result_listener << "missing expected has_reload_action";
+  if (!arg.has_fetch_action()) {
+    *result_listener << "missing expected has_fetc_action";
     return false;
   }
   if (arg.has_view_all_action()) {
@@ -558,8 +554,7 @@
     } else if (category.category == Category::FromRemoteCategory(2)) {
       ASSERT_THAT(articles.size(), Eq(1u));
       EXPECT_THAT(articles[0]->url().spec(), Eq("http://localhost/foo2"));
-      EXPECT_THAT(category.info.has_more_action(), Eq(true));
-      EXPECT_THAT(category.info.has_reload_action(), Eq(true));
+      EXPECT_THAT(category.info.has_fetch_action(), Eq(true));
       EXPECT_THAT(category.info.has_view_all_action(), Eq(false));
       EXPECT_THAT(category.info.show_if_empty(), Eq(false));
     } else {
@@ -611,7 +606,7 @@
 
   ASSERT_TRUE(fetched_categories);
   ASSERT_THAT(fetched_categories->size(), Eq(1u));
-  EXPECT_THAT(fetched_categories->front().info.has_more_action(), Eq(false));
+  EXPECT_THAT(fetched_categories->front().info.has_fetch_action(), Eq(false));
   EXPECT_THAT(fetched_categories->front().info.title(),
               Eq(base::UTF8ToUTF16("Articles for Me")));
 }
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
index 719a46b..1459386 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
@@ -1256,7 +1256,7 @@
     dict->SetBoolean(kCategoryContentProvidedByServer,
                      content.included_in_last_server_response);
     dict->SetBoolean(kCategoryContentAllowFetchingMore,
-                     content.info.has_more_action());
+                     content.info.has_fetch_action());
     list.Append(std::move(dict));
   }
   // Finally, store the result in the pref service.
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
index 32c7a68..84b83172 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
@@ -588,8 +588,7 @@
   CategoryInfo info_before = service->GetCategoryInfo(articles_category());
   ASSERT_THAT(info_before.title(), Not(IsEmpty()));
   ASSERT_THAT(info_before.title(), Not(Eq(test_default_title)));
-  EXPECT_THAT(info_before.has_more_action(), Eq(true));
-  EXPECT_THAT(info_before.has_reload_action(), Eq(true));
+  EXPECT_THAT(info_before.has_fetch_action(), Eq(true));
   EXPECT_THAT(info_before.has_view_all_action(), Eq(false));
   EXPECT_THAT(info_before.show_if_empty(), Eq(true));
 
@@ -606,8 +605,7 @@
   CategoryInfo info_with_title = service->GetCategoryInfo(articles_category());
   EXPECT_THAT(info_before.title(), Not(Eq(info_with_title.title())));
   EXPECT_THAT(test_default_title, Eq(info_with_title.title()));
-  EXPECT_THAT(info_before.has_more_action(), Eq(true));
-  EXPECT_THAT(info_before.has_reload_action(), Eq(true));
+  EXPECT_THAT(info_before.has_fetch_action(), Eq(true));
   EXPECT_THAT(info_before.has_view_all_action(), Eq(false));
   EXPECT_THAT(info_before.show_if_empty(), Eq(true));
 }
@@ -663,8 +661,7 @@
 TEST_F(RemoteSuggestionsProviderImplTest, ArticleCategoryInfo) {
   auto service = MakeSuggestionsProvider();
   CategoryInfo article_info = service->GetCategoryInfo(articles_category());
-  EXPECT_THAT(article_info.has_more_action(), Eq(true));
-  EXPECT_THAT(article_info.has_reload_action(), Eq(true));
+  EXPECT_THAT(article_info.has_fetch_action(), Eq(true));
   EXPECT_THAT(article_info.has_view_all_action(), Eq(false));
   EXPECT_THAT(article_info.show_if_empty(), Eq(true));
 }
@@ -681,8 +678,7 @@
   LoadFromJSONString(service.get(), json_str);
 
   CategoryInfo info = service->GetCategoryInfo(unknown_category());
-  EXPECT_THAT(info.has_more_action(), Eq(false));
-  EXPECT_THAT(info.has_reload_action(), Eq(false));
+  EXPECT_THAT(info.has_fetch_action(), Eq(false));
   EXPECT_THAT(info.has_view_all_action(), Eq(false));
   EXPECT_THAT(info.show_if_empty(), Eq(false));
 }
diff --git a/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc b/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
index 0d05469f..0e13fb1 100644
--- a/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
+++ b/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
@@ -199,8 +199,7 @@
   return CategoryInfo(l10n_util::GetStringUTF16(
                           IDS_NTP_FOREIGN_SESSIONS_SUGGESTIONS_SECTION_HEADER),
                       ContentSuggestionsCardLayout::MINIMAL_CARD,
-                      /*has_more_action=*/false,
-                      /*has_reload_action=*/false,
+                      /*has_fetch_action=*/false,
                       /*has_view_all_action=*/true,
                       /*show_if_empty=*/false,
                       l10n_util::GetStringUTF16(
diff --git a/components/password_manager/content/browser/PRESUBMIT.py b/components/password_manager/content/browser/PRESUBMIT.py
new file mode 100644
index 0000000..1507afc4
--- /dev/null
+++ b/components/password_manager/content/browser/PRESUBMIT.py
@@ -0,0 +1,28 @@
+# 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.
+
+"""Chromium presubmit script to check that BadMessage enums in histograms.xml
+match the corresponding bad_message.h file.
+"""
+
+def _RunHistogramChecks(input_api, output_api, histogram_name):
+  try:
+    # Setup sys.path so that we can call histrogram code
+    import sys
+    original_sys_path = sys.path
+    sys.path = sys.path + [input_api.os_path.join(
+      input_api.change.RepositoryRoot(),
+      'tools', 'metrics', 'histograms')]
+
+    import presubmit_bad_message_reasons
+    return presubmit_bad_message_reasons.PrecheckBadMessage(input_api,
+      output_api, histogram_name)
+  except:
+    return [output_api.PresubmitError('Could not verify histogram!')]
+  finally:
+    sys.path = original_sys_path
+
+def CheckChangeOnUpload(input_api, output_api):
+  return _RunHistogramChecks(input_api, output_api,
+    "BadMessageReasonPasswordManager")
diff --git a/content/browser/PRESUBMIT.py b/content/browser/PRESUBMIT.py
new file mode 100644
index 0000000..67e1c2a7
--- /dev/null
+++ b/content/browser/PRESUBMIT.py
@@ -0,0 +1,28 @@
+# 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.
+
+"""Chromium presubmit script to check that BadMessage enums in histograms.xml
+match the corresponding bad_message.h file.
+"""
+
+def _RunHistogramChecks(input_api, output_api, histogram_name):
+  try:
+    # Setup sys.path so that we can call histrogram code
+    import sys
+    original_sys_path = sys.path
+    sys.path = sys.path + [input_api.os_path.join(
+      input_api.change.RepositoryRoot(),
+      'tools', 'metrics', 'histograms')]
+
+    import presubmit_bad_message_reasons
+    return presubmit_bad_message_reasons.PrecheckBadMessage(input_api,
+      output_api, histogram_name)
+  except Exception as e:
+    return [output_api.PresubmitError("Error verifying histogram (%s)."
+      % str(e))]
+  finally:
+    sys.path = original_sys_path
+
+def CheckChangeOnUpload(input_api, output_api):
+  return _RunHistogramChecks(input_api, output_api, "BadMessageReasonContent")
diff --git a/content/browser/bluetooth/bluetooth_metrics.cc b/content/browser/bluetooth/bluetooth_metrics.cc
index 84421ee..d9ae54fb 100644
--- a/content/browser/bluetooth/bluetooth_metrics.cc
+++ b/content/browser/bluetooth/bluetooth_metrics.cc
@@ -283,6 +283,9 @@
     case UMAGATTOperation::DESCRIPTOR_READ:
       RecordDescriptorReadValueOutcome(outcome);
       return;
+    case UMAGATTOperation::DESCRIPTOR_WRITE:
+      RecordDescriptorWriteValueOutcome(outcome);
+      return;
     case UMAGATTOperation::COUNT:
       NOTREACHED();
       return;
@@ -368,6 +371,19 @@
       TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
 }
 
+// Descriptor.writeValue
+
+void RecordDescriptorWriteValueOutcome(UMAGATTOperationOutcome outcome) {
+  UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Descriptor.WriteValue.Outcome",
+                            static_cast<int>(outcome),
+                            static_cast<int>(UMAGATTOperationOutcome::COUNT));
+}
+
+void RecordDescriptorWriteValueOutcome(CacheQueryOutcome outcome) {
+  RecordDescriptorWriteValueOutcome(
+      TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
+}
+
 void RecordRSSISignalStrengthLevel(UMARSSISignalStrengthLevel level) {
   UMA_HISTOGRAM_ENUMERATION(
       "Bluetooth.Web.RequestDevice.RSSISignalStrengthLevel",
diff --git a/content/browser/bluetooth/bluetooth_metrics.h b/content/browser/bluetooth/bluetooth_metrics.h
index 65ad7b4..64c10887 100644
--- a/content/browser/bluetooth/bluetooth_metrics.h
+++ b/content/browser/bluetooth/bluetooth_metrics.h
@@ -36,6 +36,7 @@
   SERVICE_GET_CHARACTERISTICS = 9,
   GET_PRIMARY_SERVICES = 10,
   DESCRIPTOR_READ_VALUE = 11,
+  DESCRIPTOR_WRITE_VALUE = 12,
   // NOTE: Add new actions immediately above this line. Make sure to update
   // the enum list in tools/metrics/histograms/histograms.xml accordingly.
   COUNT
@@ -239,6 +240,7 @@
   CHARACTERISTIC_WRITE,
   START_NOTIFICATIONS,
   DESCRIPTOR_READ,
+  DESCRIPTOR_WRITE,
   // Note: Add new GATT Operations immediately above this line.
   COUNT
 };
@@ -289,6 +291,16 @@
 // QueryCacheForDescriptor fails.
 void RecordDescriptorReadValueOutcome(CacheQueryOutcome outcome);
 
+// Descriptor.writeValue() Metrics
+// There should be a call to this function for every call to
+// Send(BluetoothMsg_ReadDescriptorValueSuccess) and
+// Send(BluetoothMsg_ReadDescriptorValueError).
+void RecordDescriptorWriteValueOutcome(UMAGATTOperationOutcome error);
+
+// Records the outcome of a cache query for writeValue. Should only be called if
+// QueryCacheForDescriptor fails.
+void RecordDescriptorWriteValueOutcome(CacheQueryOutcome outcome);
+
 enum class UMARSSISignalStrengthLevel {
   LESS_THAN_OR_EQUAL_TO_MIN_RSSI,
   LEVEL_0,
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index 5adce0e..be6853b 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -775,6 +775,49 @@
                  weak_ptr_factory_.GetWeakPtr(), callback));
 }
 
+void WebBluetoothServiceImpl::RemoteDescriptorWriteValue(
+    const std::string& descriptor_instance_id,
+    const std::vector<uint8_t>& value,
+    const RemoteDescriptorWriteValueCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  RecordWebBluetoothFunctionCall(
+      UMAWebBluetoothFunction::DESCRIPTOR_WRITE_VALUE);
+
+  // We perform the length check on the renderer side. So if we
+  // get a value with length > 512, we can assume it's a hostile
+  // renderer and kill it.
+  if (value.size() > 512) {
+    CrashRendererAndClosePipe(bad_message::BDH_INVALID_WRITE_VALUE_LENGTH);
+    return;
+  }
+
+  const CacheQueryResult query_result =
+      QueryCacheForDescriptor(descriptor_instance_id);
+
+  if (query_result.outcome == CacheQueryOutcome::BAD_RENDERER) {
+    return;
+  }
+
+  if (query_result.outcome != CacheQueryOutcome::SUCCESS) {
+    RecordDescriptorWriteValueOutcome(query_result.outcome);
+    callback.Run(query_result.GetWebResult());
+    return;
+  }
+
+  if (BluetoothBlocklist::Get().IsExcludedFromWrites(
+          query_result.descriptor->GetUUID())) {
+    RecordDescriptorWriteValueOutcome(UMAGATTOperationOutcome::BLOCKLISTED);
+    callback.Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
+    return;
+  }
+
+  query_result.descriptor->WriteRemoteDescriptor(
+      value, base::Bind(&WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess,
+                        weak_ptr_factory_.GetWeakPtr(), callback),
+      base::Bind(&WebBluetoothServiceImpl::OnDescriptorWriteValueFailed,
+                 weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
 void WebBluetoothServiceImpl::RequestDeviceImpl(
     blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
     const RequestDeviceCallback& callback,
@@ -993,6 +1036,22 @@
                base::nullopt /* value */);
 }
 
+void WebBluetoothServiceImpl::OnDescriptorWriteValueSuccess(
+    const RemoteDescriptorWriteValueCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  // TODO(667319): We are reporting failures to UMA but not reporting successes
+  callback.Run(blink::mojom::WebBluetoothResult::SUCCESS);
+}
+
+void WebBluetoothServiceImpl::OnDescriptorWriteValueFailed(
+    const RemoteDescriptorWriteValueCallback& callback,
+    device::BluetoothRemoteGattService::GattErrorCode error_code) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  RecordDescriptorWriteValueOutcome(UMAGATTOperationOutcome::SUCCESS);
+  callback.Run(TranslateGATTErrorAndRecord(error_code,
+                                           UMAGATTOperation::DESCRIPTOR_WRITE));
+}
+
 CacheQueryResult WebBluetoothServiceImpl::QueryCacheForDevice(
     const WebBluetoothDeviceId& device_id) {
   const std::string& device_address =
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.h b/content/browser/bluetooth/web_bluetooth_service_impl.h
index c8a4c59..21660329 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.h
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.h
@@ -136,6 +136,10 @@
   void RemoteDescriptorReadValue(
       const std::string& characteristic_instance_id,
       const RemoteDescriptorReadValueCallback& callback) override;
+  void RemoteDescriptorWriteValue(
+      const std::string& descriptor_instance_id,
+      const std::vector<uint8_t>& value,
+      const RemoteDescriptorWriteValueCallback& callback) override;
 
   void RequestDeviceImpl(
       blink::mojom::WebBluetoothRequestDeviceOptionsPtr options,
@@ -206,6 +210,13 @@
       const RemoteDescriptorReadValueCallback& callback,
       device::BluetoothRemoteGattService::GattErrorCode error_code);
 
+  // Callbacks for BluetoothRemoteGattDescriptor::WriteRemoteDescriptor.
+  void OnDescriptorWriteValueSuccess(
+      const RemoteDescriptorWriteValueCallback& callback);
+  void OnDescriptorWriteValueFailed(
+      const RemoteDescriptorWriteValueCallback& callback,
+      device::BluetoothRemoteGattService::GattErrorCode error_code);
+
   // Functions to query the platform cache for the bluetooth object.
   // result.outcome == CacheQueryOutcome::SUCCESS if the object was found in the
   // cache. Otherwise result.outcome that can used to record the outcome and
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
index 57d92fd..bfa7e4a 100644
--- a/content/browser/frame_host/frame_tree_node.cc
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -7,6 +7,7 @@
 #include <queue>
 #include <utility>
 
+#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index f0f2743d..c61d97f 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -57,7 +57,6 @@
 #include "content/public/common/service_names.mojom.h"
 #include "gpu/command_buffer/service/gpu_preferences.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
-#include "gpu/config/gpu_driver_bug_list.h"
 #include "gpu/ipc/host/shader_disk_cache.h"
 #include "gpu/ipc/service/switches.h"
 #include "ipc/ipc_channel_handle.h"
@@ -1057,11 +1056,6 @@
       browser_command_line, switches::kGLSwitchesCopiedFromGpuProcessHost,
       switches::kGLSwitchesCopiedFromGpuProcessHostNumSwitches);
 
-  std::vector<const char*> gpu_workarounds;
-  gpu::GpuDriverBugList::AppendAllWorkarounds(&gpu_workarounds);
-  cmd_line->CopySwitchesFrom(browser_command_line,
-      gpu_workarounds.data(), gpu_workarounds.size());
-
   GetContentClient()->browser()->AppendExtraCommandLineSwitches(
       cmd_line.get(), process_->GetData().id);
 
diff --git a/content/browser/renderer_host/media/audio_input_device_manager.cc b/content/browser/renderer_host/media/audio_input_device_manager.cc
index aa47a6d..d2db4e1 100644
--- a/content/browser/renderer_host/media/audio_input_device_manager.cc
+++ b/content/browser/renderer_host/media/audio_input_device_manager.cc
@@ -153,8 +153,7 @@
   out.device.matched_output_device_id =
       audio_manager_->GetAssociatedOutputDeviceID(info.device.id);
 
-  if (info.device.type == MEDIA_TAB_AUDIO_CAPTURE ||
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kUseFakeDeviceForMediaStream)) {
     // Don't need to query the hardware information if using fake device.
     input_params.sample_rate = 44100;
@@ -164,6 +163,17 @@
       out.device.matched_output.channel_layout = media::CHANNEL_LAYOUT_STEREO;
     }
   } else {
+    // TODO(tommi): As is, we hit this code path when device.type is
+    // MEDIA_TAB_AUDIO_CAPTURE and the device id is not a device that
+    // the AudioManager can know about. This currently does not fail because
+    // the implementation of GetInputStreamParameters returns valid parameters
+    // by default for invalid devices. That behavior is problematic because it
+    // causes other parts of the code to attempt to open truly invalid or
+    // missing devices and falling back on alternate devices (and likely fail
+    // twice in a row). Tab audio capture should not pass through here and
+    // GetInputStreamParameters should return invalid parameters for invalid
+    // devices.
+
     // Get the preferred sample rate and channel configuration for the
     // audio device.
     media::AudioParameters params =
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/PhoneNumberDetectionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/PhoneNumberDetectionTest.java
index f292a16..ce9bf63 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/PhoneNumberDetectionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/PhoneNumberDetectionTest.java
@@ -146,6 +146,7 @@
 
     @MediumTest
     @Feature({"ContentDetection", "TabContents"})
+    @DisabledTest(message = "crbug.com/673279")
     @CommandLineFlags.Add(ContentSwitches.NETWORK_COUNTRY_ISO + "=US")
     public void testLocalUSNumbers() throws Throwable {
         startActivityWithTestUrl(
diff --git a/content/renderer/presentation/presentation_connection_proxy_unittest.cc b/content/renderer/presentation/presentation_connection_proxy_unittest.cc
index 03f0bcc5..1a94178 100644
--- a/content/renderer/presentation/presentation_connection_proxy_unittest.cc
+++ b/content/renderer/presentation/presentation_connection_proxy_unittest.cc
@@ -98,7 +98,7 @@
   base::RunLoop run_loop;
   EXPECT_CALL(*receiver_connection_, didReceiveBinaryMessage(_, _))
       .WillOnce(::testing::Invoke(
-          [this, &expected_data](const uint8_t* data, size_t length) {
+          [&expected_data](const uint8_t* data, size_t length) {
             std::vector<uint8_t> message_data(data, data + length);
             EXPECT_EQ(expected_data, message_data);
           }));
diff --git a/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.cc b/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.cc
index cf5f88a6..4df4e49c 100644
--- a/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.cc
+++ b/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.cc
@@ -759,21 +759,31 @@
           callback.Run(value);
         }));
 
+    ON_CALL(*user_description, WriteRemoteDescriptor(_, _, _))
+        .WillByDefault(RunCallback<1 /* success_callback */>());
+
     auto client_config = base::MakeUnique<NiceMockBluetoothGattDescriptor>(
         measurement_interval.get(), "gatt.client_characteristic_configuration",
         BluetoothUUID(kClientConfigUUID), false /* is_local */,
         device::BluetoothRemoteGattCharacteristic::PROPERTY_READ |
             device::BluetoothRemoteGattCharacteristic::PROPERTY_WRITE);
 
+   // Crash if WriteRemoteDescriptor called. Not using GoogleMock's Expect
+   // because this is used in layout tests that may not report a mock
+   // expectation.
+    ON_CALL(*client_config, WriteRemoteDescriptor(_, _, _))
+        .WillByDefault(
+            Invoke([](const std::vector<uint8_t>&, const base::Closure&,
+                      const BluetoothRemoteGattDescriptor::ErrorCallback&) {
+              NOTREACHED();
+            }));
+
     auto no_read_descriptor = base::MakeUnique<NiceMockBluetoothGattDescriptor>(
         measurement_interval.get(), kBlocklistedReadDescriptorUUID,
         BluetoothUUID(kBlocklistedReadDescriptorUUID), false,
         device::BluetoothRemoteGattCharacteristic::PROPERTY_READ |
             device::BluetoothRemoteGattCharacteristic::PROPERTY_WRITE);
 
-    std::vector<uint8_t> value(1);
-    value[0] = false;
-
     // Crash if ReadRemoteDescriptor called. Not using GoogleMock's Expect
     // because this is used in layout tests that may not report a mock
     // expectation
@@ -1132,6 +1142,28 @@
         }
       }));
 
+  ON_CALL(*user_descriptor, WriteRemoteDescriptor(_, _, _))
+      .WillByDefault(Invoke([adapter_ptr, device_ptr, user_descriptor_ptr,
+                             disconnect, succeeds](
+          const std::vector<uint8_t>& value, const base::Closure& callback,
+          const BluetoothRemoteGattDescriptor::ErrorCallback& error_callback) {
+        base::Closure pending;
+        if (succeeds) {
+          pending = callback;
+        } else {
+          pending = base::Bind(error_callback,
+                               BluetoothRemoteGattService::GATT_ERROR_FAILED);
+        }
+        device_ptr->PushPendingCallback(pending);
+        if (disconnect) {
+          device_ptr->SetConnected(false);
+          base::ThreadTaskRunnerHandle::Get()->PostTask(
+              FROM_HERE,
+              base::Bind(&NotifyDeviceChanged, base::RetainedRef(adapter_ptr),
+                         device_ptr));
+        }
+      }));
+
   measurement_interval->AddMockDescriptor(std::move(user_descriptor));
 
   health_thermometer->AddMockCharacteristic(std::move(measurement_interval));
@@ -1731,6 +1763,9 @@
   ON_CALL(*error_descriptor, ReadRemoteDescriptor(_, _))
       .WillByDefault(RunCallback<1 /* error_callback */>(error_code));
 
+  ON_CALL(*error_descriptor, WriteRemoteDescriptor(_, _, _))
+      .WillByDefault(RunCallback<2 /* error_callback */>(error_code));
+
   characteristic->AddMockDescriptor(std::move(error_descriptor));
 
   return characteristic;
diff --git a/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h b/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h
index a770c8b8..5343b92 100644
--- a/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h
+++ b/content/shell/browser/layout_test/layout_test_bluetooth_adapter_provider.h
@@ -343,12 +343,16 @@
   //               BluetoothRemoteGattCharacteristic::PROPERTY_READ
   //           - Descriptors (if |addDescriptors| input is true)
   //             - User Description (2901)
+  //                 - Mock Functions:
+  //                   - Read: Calls success callback with
+  //                           "gatt.characteristic_user_description".
+  //                   - Write: Calls success callback.
   //             - Client Characteristic Configuration (2902)
   //                 Note: This descriptor is blocklisted for writes.
   //             - bad2ddcf-60db-45cd-bef9-fd72b153cf7c
   //                 A test descriptor that is blocklisted.
   //             - bad3ec61-3cc3-4954-9702-7977df514114
-  //                 A test descriptor that is exclude read..
+  //                 A test descriptor that is exclude read.
 
   static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
   GetDisconnectingHealthThermometer(bool add_descriptors);
@@ -400,6 +404,13 @@
   //                 succeeding callback, otherwise it saves a failing callback.
   //                 This calback is run during CreateGattConnection. If
   //                 |disconnect| is true disconnects the device.
+  //               - user_descriptor
+  //                 - Operations read / write nearly identical to the read and
+  //                   write methods of the characteristic.
+  //                 - Read: If |succeeds| is true, saves a succeeding callback,
+  //                   otherwise it saves a failing callback.
+  //                 - Write: If |succeeds| is true, saves a succeeding callback
+  //                   otherwise it saves a failing callback.
   //         - CreateGattConnection: Runs success callback with a new GATT
   //           connection and runs any pending GATT operation callbacks.
   static scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
diff --git a/content/test/gpu/gpu_tests/gpu_process_integration_test.py b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
index 225c3dd..89691ed 100644
--- a/content/test/gpu/gpu_tests/gpu_process_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
@@ -115,8 +115,7 @@
              ('GpuProcess_identify_active_gpu2', 'chrome:gpu'),
              ('GpuProcess_identify_active_gpu3', 'chrome:gpu'),
              ('GpuProcess_identify_active_gpu4', 'chrome:gpu'),
-             ('GpuProcess_software_gpu_process', 'about:blank'),
-             ('GpuProcess_disabling_workarounds_works', 'chrome:gpu'))
+             ('GpuProcess_software_gpu_process', 'about:blank'))
 
     # The earlier has_transparent_visuals_gpu_process and
     # no_transparent_visuals_gpu_process tests became no-ops in
@@ -527,17 +526,6 @@
     self._Navigate(test_path)
     self._VerifyGpuProcessPresent()
 
-  def _GpuProcess_disabling_workarounds_works(self, test_path):
-    self.RestartBrowserIfNecessaryWithArgs([
-      '--gpu-testing-vendor-id=0xbad9',
-      '--gpu-testing-device-id=0xbad9',
-      '--use_gpu_driver_workaround_for_testing=0'])
-    self._Navigate(test_path)
-    workarounds, _ = (
-      self._CompareAndCaptureDriverBugWorkarounds())
-    if 'use_gpu_driver_workaround_for_testing' in workarounds:
-      self.fail('use_gpu_driver_workaround_for_testing erroneously present')
-
 def load_tests(loader, tests, pattern):
   del loader, tests, pattern  # Unused.
   return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])
diff --git a/content/utility/in_process_utility_thread.cc b/content/utility/in_process_utility_thread.cc
index 426326b..8417c599 100644
--- a/content/utility/in_process_utility_thread.cc
+++ b/content/utility/in_process_utility_thread.cc
@@ -4,6 +4,7 @@
 
 #include "content/utility/in_process_utility_thread.h"
 
+#include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
diff --git a/extensions/browser/PRESUBMIT.py b/extensions/browser/PRESUBMIT.py
index 15f8d93..5bd5fad 100644
--- a/extensions/browser/PRESUBMIT.py
+++ b/extensions/browser/PRESUBMIT.py
@@ -34,10 +34,27 @@
     results += _CreateHistogramValueChecker(input_api, output_api, path).Run()
   return results
 
+def _RunHistogramChecks(input_api, output_api, histogram_name):
+  try:
+    # Setup sys.path so that we can call histrogram code
+    import sys
+    original_sys_path = sys.path
+    sys.path = sys.path + [input_api.os_path.join(
+      input_api.change.RepositoryRoot(),
+      'tools', 'metrics', 'histograms')]
+
+    import presubmit_bad_message_reasons
+    return presubmit_bad_message_reasons.PrecheckBadMessage(input_api,
+      output_api, histogram_name)
+  except:
+    return [output_api.PresubmitError('Could not verify histogram!')]
+  finally:
+    sys.path = original_sys_path
 
 def CheckChangeOnUpload(input_api, output_api):
   results = []
   results += _RunHistogramValueCheckers(input_api, output_api)
   results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+  results += _RunHistogramChecks(input_api, output_api,
+    "BadMessageReasonExtensions")
   return results
-
diff --git a/extensions/browser/guest_view/extension_options/extension_options_guest.cc b/extensions/browser/guest_view/extension_options/extension_options_guest.cc
index 12dd8f3..2e466c92 100644
--- a/extensions/browser/guest_view/extension_options/extension_options_guest.cc
+++ b/extensions/browser/guest_view/extension_options/extension_options_guest.cc
@@ -11,7 +11,7 @@
 #include "components/crx_file/id_util.h"
 #include "components/guest_view/browser/guest_view_event.h"
 #include "components/guest_view/browser/guest_view_manager.h"
-#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
@@ -220,20 +220,21 @@
   return false;
 }
 
-void ExtensionOptionsGuest::DidNavigateMainFrame(
-    const content::LoadCommittedDetails& details,
-    const content::FrameNavigateParams& params) {
-  if (attached()) {
-    auto* guest_zoom_controller =
-        zoom::ZoomController::FromWebContents(web_contents());
-    guest_zoom_controller->SetZoomMode(
-        zoom::ZoomController::ZOOM_MODE_ISOLATED);
-    SetGuestZoomLevelToMatchEmbedder();
+void ExtensionOptionsGuest::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame() ||
+      !navigation_handle->HasCommitted() || !attached()) {
+    return;
+  }
 
-    if (!url::IsSameOriginWith(params.url, options_page_)) {
-      bad_message::ReceivedBadMessage(web_contents()->GetRenderProcessHost(),
-                                      bad_message::EOG_BAD_ORIGIN);
-    }
+  auto* guest_zoom_controller =
+      zoom::ZoomController::FromWebContents(web_contents());
+  guest_zoom_controller->SetZoomMode(zoom::ZoomController::ZOOM_MODE_ISOLATED);
+  SetGuestZoomLevelToMatchEmbedder();
+
+  if (!url::IsSameOriginWith(navigation_handle->GetURL(), options_page_)) {
+    bad_message::ReceivedBadMessage(web_contents()->GetRenderProcessHost(),
+                                    bad_message::EOG_BAD_ORIGIN);
   }
 }
 
diff --git a/extensions/browser/guest_view/extension_options/extension_options_guest.h b/extensions/browser/guest_view/extension_options/extension_options_guest.h
index 5b0505c..b3b714e 100644
--- a/extensions/browser/guest_view/extension_options/extension_options_guest.h
+++ b/extensions/browser/guest_view/extension_options/extension_options_guest.h
@@ -57,8 +57,7 @@
       content::SessionStorageNamespace* session_storage_namespace) final;
 
   // content::WebContentsObserver implementation.
-  void DidNavigateMainFrame(const content::LoadCommittedDetails& details,
-                            const content::FrameNavigateParams& params) final;
+  void DidFinishNavigation(content::NavigationHandle* navigation_handle) final;
 
   std::unique_ptr<extensions::ExtensionOptionsGuestDelegate>
       extension_options_guest_delegate_;
diff --git a/extensions/common/feature_switch.cc b/extensions/common/feature_switch.cc
index 84eaa904..4d1f414 100644
--- a/extensions/common/feature_switch.cc
+++ b/extensions/common/feature_switch.cc
@@ -20,8 +20,6 @@
 const char kLoadMediaRouterComponentExtensionFlag[] =
     "load-media-router-component-extension";
 
-const char kExtensionActionRedesignExperiment[] = "ExtensionActionRedesign";
-
 class CommonSwitches {
  public:
   CommonSwitches()
@@ -42,9 +40,7 @@
         error_console(switches::kErrorConsole, FeatureSwitch::DEFAULT_DISABLED),
         enable_override_bookmarks_ui(switches::kEnableOverrideBookmarksUI,
                                      FeatureSwitch::DEFAULT_DISABLED),
-        extension_action_redesign(switches::kExtensionActionRedesign,
-                                  kExtensionActionRedesignExperiment,
-                                  FeatureSwitch::DEFAULT_ENABLED),
+        extension_action_redesign(nullptr, FeatureSwitch::DEFAULT_ENABLED),
         scripts_require_action(switches::kScriptsRequireAction,
                                FeatureSwitch::DEFAULT_DISABLED),
         embedded_extension_options(switches::kEmbeddedExtensionOptions,
diff --git a/extensions/common/switches.cc b/extensions/common/switches.cc
index e867a3a6..3a5bc085 100644
--- a/extensions/common/switches.cc
+++ b/extensions/common/switches.cc
@@ -35,11 +35,6 @@
 const char kEnableExperimentalExtensionApis[] =
     "enable-experimental-extension-apis";
 
-// Hack so that feature switch can work with about_flags. See
-// kEnableScriptsRequireAction.
-const char kEnableExtensionActionRedesign[] =
-    "enable-extension-action-redesign";
-
 // Enables extensions to hide bookmarks UI elements.
 const char kEnableOverrideBookmarksUI[] = "enable-override-bookmarks-ui";
 
@@ -50,9 +45,6 @@
 // them in the chrome:extensions page.
 const char kErrorConsole[] = "error-console";
 
-// Whether to switch to extension action redesign mode (experimental).
-const char kExtensionActionRedesign[] = "extension-action-redesign";
-
 // Marks a renderer as extension process.
 const char kExtensionProcess[] = "extension-process";
 
diff --git a/extensions/common/switches.h b/extensions/common/switches.h
index 764aa99..90a142f 100644
--- a/extensions/common/switches.h
+++ b/extensions/common/switches.h
@@ -18,11 +18,9 @@
 extern const char kEmbeddedExtensionOptions[];
 extern const char kEnableEmbeddedExtensionOptions[];
 extern const char kEnableExperimentalExtensionApis[];
-extern const char kEnableExtensionActionRedesign[];
 extern const char kEnableOverrideBookmarksUI[];
 extern const char kEnableBLEAdvertising[];
 extern const char kErrorConsole[];
-extern const char kExtensionActionRedesign[];
 extern const char kExtensionProcess[];
 extern const char kExtensionsOnChromeURLs[];
 extern const char kForceDevModeHighlighting[];
diff --git a/gpu/config/gpu_driver_bug_list.cc b/gpu/config/gpu_driver_bug_list.cc
index edd9dcf..7ab68a10 100644
--- a/gpu/config/gpu_driver_bug_list.cc
+++ b/gpu/config/gpu_driver_bug_list.cc
@@ -94,17 +94,5 @@
   }
 }
 
-// static
-void GpuDriverBugList::AppendAllWorkarounds(
-    std::vector<const char*>* workarounds) {
-  workarounds->resize(workarounds->size() +
-      NUMBER_OF_GPU_DRIVER_BUG_WORKAROUND_TYPES);
-
-#define GPU_OP(type, name) workarounds->push_back(#name);
-  GPU_DRIVER_BUG_WORKAROUNDS(GPU_OP)
-#undef GPU_OP
-}
-
-
 }  // namespace gpu
 
diff --git a/gpu/config/gpu_driver_bug_list.h b/gpu/config/gpu_driver_bug_list.h
index 0ec839e..8db219f 100644
--- a/gpu/config/gpu_driver_bug_list.h
+++ b/gpu/config/gpu_driver_bug_list.h
@@ -28,11 +28,6 @@
       std::set<int>* workarounds,
       const base::CommandLine& command_line);
 
-  // Append |workarounds| with the full list of workarounds.
-  // This is needed for correctly passing flags down from
-  // the browser process to the GPU process.
-  static void AppendAllWorkarounds(std::vector<const char*>* workarounds);
-
  private:
   GpuDriverBugList();
 
diff --git a/gpu/config/gpu_driver_bug_list_json.cc b/gpu/config/gpu_driver_bug_list_json.cc
index 2ff6190a..dc2c7a8 100644
--- a/gpu/config/gpu_driver_bug_list_json.cc
+++ b/gpu/config/gpu_driver_bug_list_json.cc
@@ -19,7 +19,7 @@
 {
   "name": "gpu driver bug list",
   // Please update the version number whenever you change this file.
-  "version": "9.30",
+  "version": "9.29",
   "entries": [
     {
       "id": 1,
@@ -2324,9 +2324,6 @@
         "use_virtualized_gl_contexts"
       ]
     },
-)  // LONG_STRING_CONST macro
-// Avoid C2026 (string too big) error on VisualStudio.
-LONG_STRING_CONST(
     {
       "id": 214,
       "description": "Certain versions of Qualcomm driver don't setup scissor state correctly when FBO0 is bound.",
@@ -2336,16 +2333,6 @@
       "features": [
         "force_update_scissor_state_when_binding_fbo0"
       ]
-    },
-    {
-      "id": 215,
-      "description": "Fake no-op GPU driver bug workaround for testing",
-      "cr_bugs": [682912],
-      "vendor_id": "0xbad9",
-      "device_id": ["0xbad9"],
-      "features": [
-        "use_gpu_driver_workaround_for_testing"
-      ]
     }
   ]
   // Please update the version number at beginning of this file whenever you
diff --git a/ios/web/PRESUBMIT.py b/ios/PRESUBMIT.py
similarity index 81%
rename from ios/web/PRESUBMIT.py
rename to ios/PRESUBMIT.py
index 3d39a554..07c61c0c 100644
--- a/ios/web/PRESUBMIT.py
+++ b/ios/PRESUBMIT.py
@@ -1,8 +1,8 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# 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.
 
-"""Presubmit script for ios/web.
+"""Presubmit script for ios.
 
 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
 for more details about the presubmit API built into depot_tools.
diff --git a/ios/chrome/PRESUBMIT.py b/ios/chrome/PRESUBMIT.py
deleted file mode 100644
index 18f3a4f..0000000
--- a/ios/chrome/PRESUBMIT.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2015 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.
-
-"""Presubmit script for ios/chrome.
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
-for more details about the presubmit API built into depot_tools.
-"""
-
-def CheckChangeOnUpload(input_api, output_api):
-  results = []
-  results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
-  return results
diff --git a/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc b/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
index 918d4e1..c6decb7 100644
--- a/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
+++ b/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
@@ -74,19 +74,6 @@
 std::unique_ptr<KeyedService>
 DomDistillerServiceFactory::BuildServiceInstanceFor(
     web::BrowserState* context) const {
-  scoped_refptr<base::SequencedTaskRunner> background_task_runner =
-      web::WebThread::GetBlockingPool()->GetSequencedTaskRunner(
-          web::WebThread::GetBlockingPool()->GetSequenceToken());
-
-  std::unique_ptr<leveldb_proto::ProtoDatabaseImpl<ArticleEntry>> db =
-      base::MakeUnique<leveldb_proto::ProtoDatabaseImpl<ArticleEntry>>(
-          background_task_runner);
-
-  base::FilePath database_dir(
-      context->GetStatePath().Append(FILE_PATH_LITERAL("Articles")));
-
-  std::unique_ptr<DomDistillerStore> dom_distiller_store =
-      base::MakeUnique<DomDistillerStore>(std::move(db), database_dir);
 
   std::unique_ptr<DistillerPageFactory> distiller_page_factory =
       base::MakeUnique<DistillerPageFactoryIOS>(context);
@@ -104,8 +91,8 @@
           ios::ChromeBrowserState::FromBrowserState(context)->GetPrefs());
 
   return base::MakeUnique<DomDistillerKeyedService>(
-      std::move(dom_distiller_store), std::move(distiller_factory),
-      std::move(distiller_page_factory), std::move(distilled_page_prefs));
+      nullptr, std::move(distiller_factory), std::move(distiller_page_factory),
+      std::move(distilled_page_prefs));
 }
 
 web::BrowserState* DomDistillerServiceFactory::GetBrowserStateToUse(
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
index 96d7bb9..ff9f45e 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller.mm
@@ -578,7 +578,7 @@
   NSArray* items =
       [self.collectionViewModel itemsInSectionWithIdentifier:sectionIdentifier];
   if ([items count] != map.size())
-    return NO;
+    return YES;
 
   NSInteger index = 0;
   ItemsMapByDate::const_reverse_iterator iterator = map.rbegin();
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h
index ca03cc62..cfed23d9 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h
@@ -46,6 +46,13 @@
 // Sets the cell's appearance using information in |tab|.
 // The delegate needs to be set before calling this method.
 - (void)setAppearanceForTab:(Tab*)tab cellSize:(CGSize)cellSize;
+
+// PLACEHOLDER: Sets the cell's appearance using information in |title| and
+// |favicon|.
+- (void)setAppearanceForTabTitle:(NSString*)title
+                         favicon:(UIImage*)favicon
+                        cellSize:(CGSize)cellSize;
+
 // Sets the cell's appearance depending on |type|.
 - (void)setSessionType:(TabSwitcherSessionType)type;
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.mm
index bad9ca2..bf3f9f2 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.mm
@@ -242,6 +242,22 @@
                           }];
 }
 
+- (void)setAppearanceForTabTitle:(NSString*)title
+                         favicon:(UIImage*)favicon
+                        cellSize:(CGSize)cellSize {
+  [_titleLabel setText:title];
+  [_snapshotButton setAccessibilityIdentifier:
+                      [NSString stringWithFormat:@"%@_button", title]];
+  [self contentView].accessibilityLabel = title;
+  if (favicon) {
+    [_favicon setImage:favicon];
+  } else {
+    [_favicon setImage:NativeImage(IDR_IOS_OMNIBOX_HTTP)];
+  }
+
+  // PLACEHOLDER: Set snapshot here.
+}
+
 - (void)setSessionType:(TabSwitcherSessionType)type {
   UIColor* topBarBackgroundColor;
   UIColor* closeButtonTintColor;
diff --git a/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn b/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
index 74bd8bee..38efb26 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
@@ -39,6 +39,7 @@
 
   deps = [
     "//base",
+    "//ios/chrome/browser/ui/tab_switcher",
     "//ios/clean/chrome/browser/ui/actions",
     "//ios/clean/chrome/browser/ui/animators",
     "//ios/clean/chrome/browser/ui/commands",
diff --git a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
index f91f66e..6948934 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
@@ -51,7 +51,7 @@
   [super setBrowserState:browserState];
   // PLACEHOLDER: Generate a tab group with four empty tabs.
   self.tabGroup =
-      [TabGroup tabGroupWithEmptyTabCount:4 forBrowserState:self.browserState];
+      [TabGroup tabGroupWithEmptyTabCount:7 forBrowserState:self.browserState];
 }
 
 #pragma mark - BrowserCoordinator
@@ -81,7 +81,7 @@
   WebMediator* tab = [self.tabGroup tabAtIndex:index];
   GURL url = tab.webState->GetVisibleURL();
   NSString* urlText = @"<New Tab>";
-  if (!url.is_valid()) {
+  if (url.is_valid()) {
     urlText = base::SysUTF8ToNSString(url.spec());
   }
   return urlText;
diff --git a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index 8ebf91b..0d20e73 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -9,19 +9,19 @@
 #import "ios/clean/chrome/browser/ui/tab_grid/tab_grid_view_controller.h"
 
 #include "base/mac/foundation_util.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_collection_view_layout.h"
 #import "ios/clean/chrome/browser/ui/actions/settings_actions.h"
 #import "ios/clean/chrome/browser/ui/actions/tab_grid_actions.h"
 #import "ios/clean/chrome/browser/ui/commands/settings_commands.h"
 #import "ios/clean/chrome/browser/ui/commands/tab_commands.h"
 #import "ios/clean/chrome/browser/ui/commands/tab_grid_commands.h"
-#import "ios/clean/chrome/browser/ui/tab_grid/tab_grid_tab_cell.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
 namespace {
-NSString* const kTabGridCellIdentifier = @"tabGridCell";
 const CGFloat kSpace = 20;
 const CGFloat kTabSize = 150;
 }
@@ -29,7 +29,8 @@
 @interface TabGridViewController ()<SettingsActions,
                                     TabGridActions,
                                     UICollectionViewDataSource,
-                                    UICollectionViewDelegate>
+                                    UICollectionViewDelegate,
+                                    SessionCellDelegate>
 @property(nonatomic, weak) UICollectionView* grid;
 @end
 
@@ -68,8 +69,8 @@
     [settings.centerYAnchor constraintEqualToAnchor:stripe.centerYAnchor]
   ]];
 
-  UICollectionViewFlowLayout* layout =
-      [[UICollectionViewFlowLayout alloc] init];
+  TabSwitcherPanelCollectionViewLayout* layout =
+      [[TabSwitcherPanelCollectionViewLayout alloc] init];
   layout.minimumLineSpacing = kSpace;
   layout.minimumInteritemSpacing = kSpace;
   layout.sectionInset = UIEdgeInsetsMake(kSpace, kSpace, kSpace, kSpace);
@@ -84,8 +85,8 @@
   self.grid = grid;
   self.grid.dataSource = self;
   self.grid.delegate = self;
-  [self.grid registerClass:[TabGridTabCell class]
-      forCellWithReuseIdentifier:kTabGridCellIdentifier];
+  [self.grid registerClass:[TabSwitcherLocalSessionCell class]
+      forCellWithReuseIdentifier:[TabSwitcherLocalSessionCell identifier]];
 
   [NSLayoutConstraint activateConstraints:@[
     [self.grid.topAnchor constraintEqualToAnchor:stripe.bottomAnchor],
@@ -113,23 +114,19 @@
 
 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
                  cellForItemAtIndexPath:(nonnull NSIndexPath*)indexPath {
-  TabGridTabCell* cell =
-      base::mac::ObjCCastStrict<TabGridTabCell>([collectionView
-          dequeueReusableCellWithReuseIdentifier:kTabGridCellIdentifier
+  TabSwitcherLocalSessionCell* cell =
+      base::mac::ObjCCastStrict<TabSwitcherLocalSessionCell>([collectionView
+          dequeueReusableCellWithReuseIdentifier:
+                              [TabSwitcherLocalSessionCell identifier]
                                     forIndexPath:indexPath]);
-  cell.contentView.backgroundColor = [UIColor purpleColor];
-  cell.selected = YES;
-  cell.label.text = [self.dataSource titleAtIndex:indexPath.item];
+  cell.delegate = self;
+  [cell setSessionType:TabSwitcherSessionType::REGULAR_SESSION];
+  [cell setAppearanceForTabTitle:[self.dataSource titleAtIndex:indexPath.item]
+                         favicon:nil
+                        cellSize:CGSizeZero];
   return cell;
 }
 
-#pragma mark - UICollectionViewDelegate methods
-
-- (void)collectionView:(UICollectionView*)collectionView
-    didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
-  [self.tabCommandHandler showTabAtIndexPath:indexPath];
-}
-
 #pragma mark - ZoomTransitionDelegate methods
 
 - (CGRect)rectForZoomWithKey:(NSObject*)key inView:(UIView*)view {
@@ -152,4 +149,19 @@
   [self.tabGridCommandHandler showTabGrid];
 }
 
+#pragma mark - SessionCellDelegate
+
+- (TabSwitcherCache*)tabSwitcherCache {
+  // PLACEHOLDER: return image cache.
+  return nil;
+}
+
+- (void)cellPressed:(UICollectionViewCell*)cell {
+  [self.tabCommandHandler showTabAtIndexPath:[self.grid indexPathForCell:cell]];
+}
+
+- (void)deleteButtonPressedForCell:(UICollectionViewCell*)cell {
+  // PLACEHOLDER: handle close tab button.
+}
+
 @end
diff --git a/ios/showcase/tab_grid/sc_tab_grid_egtest.mm b/ios/showcase/tab_grid/sc_tab_grid_egtest.mm
index 68a4ad0a..e343032 100644
--- a/ios/showcase/tab_grid/sc_tab_grid_egtest.mm
+++ b/ios/showcase/tab_grid/sc_tab_grid_egtest.mm
@@ -20,7 +20,7 @@
 - (void)testLaunchAndTappingCell {
   [[EarlGrey selectElementWithMatcher:grey_text(@"TabGridViewController")]
       performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_text(@"Tab 0")]
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Tab 0_button")]
       performAction:grey_tap()];
   [[EarlGrey selectElementWithMatcher:grey_text(@"TabCommands")]
       assertWithMatcher:grey_notNil()];
diff --git a/ios/showcase/toolbar/BUILD.gn b/ios/showcase/toolbar/BUILD.gn
index c9bd5eb41..223a3d11 100644
--- a/ios/showcase/toolbar/BUILD.gn
+++ b/ios/showcase/toolbar/BUILD.gn
@@ -8,6 +8,7 @@
     "sc_toolbar_coordinator.mm",
   ]
   deps = [
+    "//ios/clean/chrome/browser/ui/commands",
     "//ios/clean/chrome/browser/ui/toolbar:toolbar_ui",
     "//ios/showcase/common",
   ]
diff --git a/ios/showcase/toolbar/sc_toolbar_coordinator.mm b/ios/showcase/toolbar/sc_toolbar_coordinator.mm
index 3291310..939ae29 100644
--- a/ios/showcase/toolbar/sc_toolbar_coordinator.mm
+++ b/ios/showcase/toolbar/sc_toolbar_coordinator.mm
@@ -4,19 +4,70 @@
 
 #import "ios/showcase/toolbar/sc_toolbar_coordinator.h"
 
+#import "ios/clean/chrome/browser/ui/commands/toolbar_commands.h"
 #import "ios/clean/chrome/browser/ui/toolbar/toolbar_view_controller.h"
+#import "ios/showcase/common/protocol_alerter.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+namespace {
+// Toolbar height.
+CGFloat kToolbarHeight = 50.0f;
+}  // namespace
+
+@interface SCToolbarCoordinator ()
+@property(nonatomic, strong) ProtocolAlerter* alerter;
+@end
+
 @implementation SCToolbarCoordinator
 @synthesize baseViewController = _baseViewController;
+@synthesize alerter = _alerter;
 
 - (void)start {
-  ToolbarViewController* viewController = [[ToolbarViewController alloc] init];
-  viewController.title = @"Toolbar";
-  [self.baseViewController pushViewController:viewController animated:YES];
+  self.alerter = [[ProtocolAlerter alloc]
+      initWithProtocols:@[ @protocol(ToolbarCommands) ]];
+  self.alerter.baseViewController = self.baseViewController;
+
+  UIViewController* containerViewController = [[UIViewController alloc] init];
+  containerViewController.view.backgroundColor = [UIColor whiteColor];
+  containerViewController.title = @"Toolbar";
+
+  UIView* containerView = [[UIView alloc] init];
+  [containerViewController.view addSubview:containerView];
+  containerView.backgroundColor = [UIColor redColor];
+  containerView.translatesAutoresizingMaskIntoConstraints = NO;
+
+  ToolbarViewController* toolbarViewController =
+      [[ToolbarViewController alloc] init];
+  toolbarViewController.toolbarCommandHandler =
+      static_cast<id<ToolbarCommands>>(self.alerter);
+  toolbarViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
+  [containerViewController addChildViewController:toolbarViewController];
+  [containerView addSubview:toolbarViewController.view];
+  [toolbarViewController didMoveToParentViewController:containerViewController];
+
+  [NSLayoutConstraint activateConstraints:@[
+    [containerView.heightAnchor constraintEqualToConstant:kToolbarHeight],
+    [containerView.leadingAnchor
+        constraintEqualToAnchor:containerViewController.view.leadingAnchor],
+    [containerView.trailingAnchor
+        constraintEqualToAnchor:containerViewController.view.trailingAnchor],
+    [containerView.centerYAnchor
+        constraintEqualToAnchor:containerViewController.view.centerYAnchor],
+    [toolbarViewController.view.topAnchor
+        constraintEqualToAnchor:containerView.topAnchor],
+    [toolbarViewController.view.bottomAnchor
+        constraintEqualToAnchor:containerView.bottomAnchor],
+    [toolbarViewController.view.leadingAnchor
+        constraintEqualToAnchor:containerView.leadingAnchor],
+    [toolbarViewController.view.trailingAnchor
+        constraintEqualToAnchor:containerView.trailingAnchor],
+  ]];
+
+  [self.baseViewController pushViewController:containerViewController
+                                     animated:YES];
 }
 
 @end
diff --git a/ios/web_view/public/criwv.h b/ios/web_view/public/criwv.h
index a858af2..f90a34a 100644
--- a/ios/web_view/public/criwv.h
+++ b/ios/web_view/public/criwv.h
@@ -11,6 +11,7 @@
 @protocol CRIWVWebView;
 
 // Main interface for the CRIWV library.
+__attribute__((visibility("default")))
 @interface CRIWV : NSObject
 
 // Initializes the CRIWV library.  This function should be called from
diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc
index b5b262fb..119ac3a2 100644
--- a/media/audio/win/audio_manager_win.cc
+++ b/media/audio/win/audio_manager_win.cc
@@ -285,7 +285,14 @@
   if (FAILED(hr) || !parameters.IsValid()) {
     LOG(WARNING) << "Unable to get preferred audio params for " << device_id
                  << " 0x" << std::hex << hr;
-    return parameters;
+    // TODO(tommi): We appear to have callers to GetInputStreamParameters that
+    // rely on getting valid audio parameters returned for an invalid or
+    // unavailable device. We should track down those code paths (it is likely
+    // that they actually don't need a real device but depend on the audio
+    // code path somehow for a configuration - e.g. tab capture).
+    parameters =
+        AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
+                        CHANNEL_LAYOUT_STEREO, 48000, 16, kFallbackBufferSize);
   }
 
   int user_buffer_size = GetUserBufferSize();
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 44ba5f1..489d717 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -26,6 +26,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/sys_info.h"
 #include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "net/base/url_util.h"
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 4089d88..08a66cc1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -734,21 +734,6 @@
             ]
         }
     ],
-    "ExtensionActionRedesign": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "win"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled"
-                }
-            ]
-        }
-    ],
     "ExtensionContentVerification": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/descriptor-is-blocklisted.html
similarity index 94%
rename from third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html
rename to third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/descriptor-is-blocklisted.html
index f33cf80..3964edb 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/descriptor-is-blocklisted.html
@@ -1,4 +1,3 @@
-<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
 <!DOCTYPE html>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-removed.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-removed.html
index 5fb1b5be..a6060802 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-removed.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-removed.html
@@ -6,6 +6,7 @@
 <script>
 'use strict';
 promise_test(() => {
+  let val = new Uint8Array([1]);
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
       .then(
           () => requestDeviceWithKeyDown(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-fails.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-fails.html
index 2b4aea8..a3bc1f11 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-fails.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-fails.html
@@ -7,6 +7,7 @@
 'use strict';
 promise_test(
     () => {
+        let val = new Uint8Array([1]);
         return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
             .then(
                 () => requestDeviceWithKeyDown(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
index 42dbf74..d379600 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-garbage-collection-ran-during-success.html
@@ -7,6 +7,7 @@
 'use strict';
 promise_test(
     () => {
+      let val = new Uint8Array([1]);
       let promise;
       return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
           .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
index 5a1516f9..c72b5ddc 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-io-op-reconnect-during-error.html
@@ -6,6 +6,7 @@
 <script>
 promise_test(
     () => {
+      let val = new Uint8Array([1]);
       return setBluetoothFakeAdapter(
                  'GATTOperationFailsAfterReconnectionAdapter')
           .then(
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-service-is-removed.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-service-is-removed.html
index 2cd9e6d..eb7f24e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-service-is-removed.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-service-is-removed.html
@@ -8,23 +8,21 @@
 promise_test(() => {
   let val = new Uint8Array([1]);
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-      .then(
-          () => requestDeviceWithKeyDown(
-              {filters: [{services: ['health_thermometer']}]}))
-      .then(device => device.gatt.connect())
-      .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
-      .then(service => service.getCharacteristic('measurement_interval'))
-      .then(
-          characteristic => characteristic.getDescriptor(user_description.name))
-      .then(descriptor => {
-        return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-            .then(
-                () => assert_promise_rejects_with_message(
-                    descriptor.readValue(),
-                    new DOMException(
-                        'GATT Service no longer exists.', 'InvalidStateError'),
-                    'Service got removed.'));
-      });
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(descriptor => {
+      return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
+        .then(() => assert_promise_rejects_with_message(
+          descriptor.readValue(),
+          new DOMException(
+            'GATT Service no longer exists.',
+            'InvalidStateError'),
+          'Service got removed.'));
+    });
 }, 'Service gets removed. Reject with InvalidStateError.');
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/descriptor-is-blocklisted.html
similarity index 70%
copy from third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html
copy to third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/descriptor-is-blocklisted.html
index f33cf80..bec0a26ad 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/descriptor/readValue/gen-descriptor-is-blocklisted.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/descriptor-is-blocklisted.html
@@ -1,4 +1,3 @@
-<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
 <!DOCTYPE html>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
@@ -7,6 +6,7 @@
 'use strict';
 promise_test(
     () => {
+      let val = new Uint8Array([1]);
         return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
             .then(
                 () => requestDeviceWithKeyDown(
@@ -17,16 +17,15 @@
                     gattServer.getPrimaryService('health_thermometer'))
             .then(service => service.getCharacteristic('measurement_interval'))
             .then(
-                characteristic => characteristic.getDescriptor(
-                    'bad3ec61-3cc3-4954-9702-7977df514114'))
+                characteristic => characteristic.getDescriptor(0x2902))
             .then(descriptor => {
               return assert_promise_rejects_with_message(
-                  descriptor.readValue(),
+                  descriptor.writeValue(val),
                   new DOMException(
-                      'readValue() called on blocklisted object marked exclude-reads. ' +
+                      'writeValue() called on blocklisted object marked exclude-writes. ' +
                           'https://goo.gl/4NeimX',
                       'SecurityError'));
             })},
-    'Attempt to call readValue on a blocked descriptor must generate a SecurityError');
+    'Attempt to call writeValue on a blocked descriptor must generate a SecurityError');
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-descriptor-is-removed.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-descriptor-is-removed.html
new file mode 100644
index 0000000..ed710b7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-descriptor-is-removed.html
@@ -0,0 +1,32 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  let val = new Uint8Array([1]);
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+      .then(service => service.getCharacteristic('measurement_interval'))
+      .then(
+          characteristic => characteristic.getDescriptor(user_description.name))
+      .then(descriptor => {
+        return setBluetoothFakeAdapter(
+                   'MissingDescriptorsDisconnectingHealthThermometerAdapter')
+            .then(
+                () => assert_promise_rejects_with_message(
+                    descriptor.writeValue(val),
+                    new DOMException(
+                        'GATT Descriptor no longer exists.',
+                        'InvalidStateError'),
+                    'Descriptor got removed.'));
+      });
+}, 'Descriptor gets removed. Reject with InvalidStateError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html
new file mode 100644
index 0000000..2decfb8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-before.html
@@ -0,0 +1,35 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  let val = new Uint8Array([1]);
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+      .then(() => requestDeviceWithKeyDown({
+              filters: [{services: ['health_thermometer']}],
+              optionalServices: [request_disconnection_service_uuid]
+            }))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        let measurement_interval;
+        return gattServer.getPrimaryService('health_thermometer')
+            .then(ht => ht.getCharacteristic('measurement_interval'))
+            .then(
+                characteristic =>
+                    characteristic.getDescriptor(user_description.name))
+            .then(d => user_description = d)
+            .then(() => get_request_disconnection(gattServer))
+            .then(requestDisconnection => requestDisconnection())
+            .then(
+                () => assert_promise_rejects_with_message(
+                    user_description.writeValue(val),
+                    new DOMException(
+                        'GATT Server is disconnected. Cannot perform GATT operations.',
+                        'NetworkError')));
+      });
+}, 'Device disconnects before writeValue. Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html
new file mode 100644
index 0000000..f1f787c6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-error.html
@@ -0,0 +1,39 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
+          .then(() => requestDeviceWithKeyDown({
+                  filters: [{services: [errorUUID(0xA0)]}],
+                  optionalServices: [request_disconnection_service_uuid]
+                }))
+          .then(device => device.gatt.connect())
+          .then(gattServer => {
+            let error_characteristic;
+            return gattServer.getPrimaryService(errorUUID(0xA0))
+                .then(es => es.getCharacteristic(errorUUID(0xA1)))
+                .then(
+                    characteristic => characteristic.getDescriptor(
+                        'gatt.characteristic_user_description'))
+                .then(d => user_description = d)
+                .then(() => get_request_disconnection(gattServer))
+                .then(requestDisconnection => {
+                  requestDisconnection();
+                  return assert_promise_rejects_with_message(
+                      user_description.writeValue(val),
+                      new DOMException(
+                          'GATT Server disconnected while performing a GATT operation.',
+                          'NetworkError'));
+                });
+          });
+    },
+    'Device disconnects during a writeValue call that fails. ' +
+        'Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html
new file mode 100644
index 0000000..25a49665
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-disconnects-during-success.html
@@ -0,0 +1,38 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+          .then(() => requestDeviceWithKeyDown({
+                  filters: [{services: ['health_thermometer']}],
+                  optionalServices: [request_disconnection_service_uuid]
+                }))
+          .then(device => device.gatt.connect())
+          .then(gattServer => {
+            return gattServer.getPrimaryService('health_thermometer')
+                .then(ht => ht.getCharacteristic('measurement_interval'))
+                .then(
+                    characteristic =>
+                        characteristic.getDescriptor(user_description.name))
+                .then(d => user_description = d)
+                .then(() => get_request_disconnection(gattServer))
+                .then(requestDisconnection => {
+                  requestDisconnection();
+                  return assert_promise_rejects_with_message(
+                      user_description.writeValue(val),
+                      new DOMException(
+                          'GATT Server disconnected while performing a GATT operation.',
+                          'NetworkError'));
+                });
+          });
+    },
+    'Device disconnects during a writeValue call that succeeds. ' +
+        'Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html
new file mode 100644
index 0000000..9933ca7c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-error.html
@@ -0,0 +1,40 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter(
+                 'DisconnectingDuringFailureGATTOperationAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(gatt => gatt.getPrimaryService('health_thermometer'))
+          .then(service => service.getCharacteristic('measurement_interval'))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(descriptor => {
+            let disconnected = eventPromise(
+                descriptor.characteristic.service.device,
+                'gattserverdisconnected');
+            let promise = assert_promise_rejects_with_message(
+                descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            return disconnected
+                .then(
+                    () =>
+                        descriptor.characteristic.service.device.gatt.connect())
+                .then(() => promise);
+          });
+    },
+    'Device reconnects during a writeValue call that fails. Reject ' +
+        'with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html
new file mode 100644
index 0000000..4251127c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-device-reconnects-during-success.html
@@ -0,0 +1,40 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter(
+                 'DisconnectingDuringSuccessGATTOperationAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(gatt => gatt.getPrimaryService('health_thermometer'))
+          .then(service => service.getCharacteristic('measurement_interval'))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(descriptor => {
+            let disconnected = eventPromise(
+                descriptor.characteristic.service.device,
+                'gattserverdisconnected');
+            let promise = assert_promise_rejects_with_message(
+                descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            return disconnected
+                .then(
+                    () =>
+                        descriptor.characteristic.service.device.gatt.connect())
+                .then(() => promise);
+          });
+    },
+    'Device reconnects during a writeValue call that succeeds. Reject ' +
+        'with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html
new file mode 100644
index 0000000..31f741d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-before.html
@@ -0,0 +1,32 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  let val = new Uint8Array([1]);
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+      .then(
+          () => requestDeviceWithKeyDown(
+              {filters: [{services: ['health_thermometer']}]}))
+      .then(device => device.gatt.connect())
+      .then(gattServer => {
+        return gattServer.getPrimaryService('health_thermometer')
+            .then(service => service.getCharacteristic('measurement_interval'))
+            .then(
+                characteristic =>
+                    characteristic.getDescriptor(user_description.name))
+            .then(descriptor => {
+              gattServer.disconnect();
+              return assert_promise_rejects_with_message(
+                  descriptor.writeValue(val),
+                  new DOMException(
+                      'GATT Server is disconnected. Cannot perform GATT operations.',
+                      'NetworkError'));
+            });
+      });
+}, 'disconnect() called before writeValue. Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html
new file mode 100644
index 0000000..5d5bc150
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-error.html
@@ -0,0 +1,36 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: [errorUUID(0xA0)]}]}))
+          .then(device => device.gatt.connect())
+          .then(gattServer => {
+            return gattServer.getPrimaryService(errorUUID(0xA0))
+                .then(service => service.getCharacteristic(errorUUID(0xA1)))
+                .then(
+                    characteristic =>
+                        characteristic.getDescriptor(user_description.name))
+                .then(descriptor => {
+                  let promise = assert_promise_rejects_with_message(
+                      descriptor.writeValue(val),
+                      new DOMException(
+                          'GATT Server disconnected while performing a GATT operation.',
+                          'NetworkError'));
+                  gattServer.disconnect();
+                  return promise;
+                });
+          });
+    },
+    'disconnect() called during a writeValue call that fails. ' +
+        'Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html
new file mode 100644
index 0000000..9e857bc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-disconnect-called-during-success.html
@@ -0,0 +1,38 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(gattServer => {
+            return gattServer.getPrimaryService('health_thermometer')
+                .then(
+                    service =>
+                        service.getCharacteristic('measurement_interval'))
+                .then(
+                    characteristic =>
+                        characteristic.getDescriptor(user_description.name))
+                .then(descriptor => {
+                  let promise = assert_promise_rejects_with_message(
+                      descriptor.writeValue(val),
+                      new DOMException(
+                          'GATT Server disconnected while performing a GATT operation.',
+                          'NetworkError'));
+                  gattServer.disconnect();
+                  return promise;
+                });
+          });
+    },
+    'disconnect() called during a writeValue call that succeeds. ' +
+        'Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-fails.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-fails.html
new file mode 100644
index 0000000..317a6d0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-fails.html
@@ -0,0 +1,39 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+        let val = new Uint8Array([1]);
+        return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
+            .then(
+                () => requestDeviceWithKeyDown(
+                    {filters: [{services: [errorUUID(0xA0)]}]}))
+            .then(device => device.gatt.connect())
+            .then(gattServer => gattServer.getPrimaryService(errorUUID(0xA0)))
+            .then(
+                service => {
+                    service.getCharacteristic(errorUUID(0xA1))
+                        .then(characteristic => {
+                          let tests = Promise.resolve();
+                          gatt_errors_tests.forEach(testSpec => {
+                            tests =
+                                tests
+                                    .then(
+                                        () => characteristic.getDescriptor(
+                                            testSpec.uuid))
+                                    .then(
+                                        descriptor =>
+                                            assert_promise_rejects_with_message(
+                                                descriptor.writeValue(val),
+                                                testSpec.error,
+                                                testSpec.testName));
+                          });
+                          return tests;
+                        })})},
+    'writeValue fails. Should reject with appropriate error.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html
new file mode 100644
index 0000000..d237ddcb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-error.html
@@ -0,0 +1,38 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      let promise;
+      return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: [errorUUID(0xA0)]}]}))
+          .then(device => device.gatt.connect())
+          .then(gattServer => gattServer.getPrimaryService(errorUUID(0xA0)))
+          .then(service => service.getCharacteristic(errorUUID(0xA1)))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(error_descriptor => {
+            promise = assert_promise_rejects_with_message(
+                error_descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            // Disconnect called to clear attributeInstanceMap and allow the
+            // object to get garbage collected.
+            error_descriptor.characteristic.service.device.gatt.disconnect();
+          })
+          .then(runGarbageCollection)
+          .then(() => promise);
+    },
+    'Garbage Collection ran during a writeValue call that fails. ' +
+        'Should not crash.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html
new file mode 100644
index 0000000..2b525fc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-garbage-collection-ran-during-success.html
@@ -0,0 +1,39 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      let promise;
+      return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(
+              gattServer => gattServer.getPrimaryService('health_thermometer'))
+          .then(service => service.getCharacteristic('measurement_interval'))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(descriptor => {
+            promise = assert_promise_rejects_with_message(
+                descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            // Disconnect called to clear attributeInstanceMap and allow the
+            // object to get garbage collected.
+            descriptor.characteristic.service.device.gatt.disconnect();
+          })
+          .then(runGarbageCollection)
+          .then(() => promise);
+    },
+    'Garbage collection ran during a writeValue call that succeeds. ' +
+        'Should not crash.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html
new file mode 100644
index 0000000..d7db91f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-error.html
@@ -0,0 +1,35 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter(
+                 'GATTOperationFailsAfterReconnectionAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(gatt => gatt.getPrimaryService('health_thermometer'))
+          .then(service => service.getCharacteristic('measurement_interval'))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(descriptor => {
+            let promise = assert_promise_rejects_with_message(
+                descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            let gatt = descriptor.characteristic.service.device.gatt;
+            gatt.disconnect();
+            return gatt.connect().then(() => promise);
+          });
+    },
+    'disconnect() and connect() called during a writeValue call that ' +
+        'fails. Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html
new file mode 100644
index 0000000..f55f7c5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-io-op-reconnect-during-success.html
@@ -0,0 +1,35 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+promise_test(
+    () => {
+      let val = new Uint8Array([1]);
+      return setBluetoothFakeAdapter(
+                 'GATTOperationSucceedsAfterReconnectionAdapter')
+          .then(
+              () => requestDeviceWithKeyDown(
+                  {filters: [{services: ['health_thermometer']}]}))
+          .then(device => device.gatt.connect())
+          .then(gatt => gatt.getPrimaryService('health_thermometer'))
+          .then(service => service.getCharacteristic('measurement_interval'))
+          .then(
+              characteristic =>
+                  characteristic.getDescriptor(user_description.name))
+          .then(descriptor => {
+            let promise = assert_promise_rejects_with_message(
+                descriptor.writeValue(val),
+                new DOMException(
+                    'GATT Server disconnected while performing a GATT operation.',
+                    'NetworkError'));
+            let gatt = descriptor.characteristic.service.device.gatt;
+            gatt.disconnect();
+            return gatt.connect().then(() => promise);
+          });
+    },
+    'disconnect() and connect() called during a writeValue call that ' +
+        'succeeds. Reject with NetworkError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-service-is-removed.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-service-is-removed.html
new file mode 100644
index 0000000..6cd2690e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/gen-service-is-removed.html
@@ -0,0 +1,28 @@
+<!-- Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py -->
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  let val = new Uint8Array([1]);
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(descriptor => {
+      return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
+        .then(() => assert_promise_rejects_with_message(
+          descriptor.writeValue(val),
+          new DOMException(
+            'GATT Service no longer exists.',
+            'InvalidStateError'),
+          'Service got removed.'));
+    });
+}, 'Service gets removed. Reject with InvalidStateError.');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/value-too-long.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/value-too-long.html
new file mode 100644
index 0000000..3e25af42
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/value-too-long.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(descriptor => {
+      return assert_promise_rejects_with_message(
+        descriptor.writeValue(new Uint8Array(513 /* length */)),
+        new DOMException(
+          'Value can\'t exceed 512 bytes.', 'InvalidModificationError'),
+        'Value passed was too long.');
+    })
+    }, 'Trying to write more than 512 bytes should return an error.');
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-succeeds.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-succeeds.html
new file mode 100644
index 0000000..e741cb6ae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-succeeds.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  let length = 1;
+  let descriptor;
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(d => descriptor = d)
+    .then(() => descriptor.writeValue(new Uint8Array(length)))
+    .then(() => descriptor.writeValue(new ArrayBuffer(length)))
+    .then(() => descriptor.writeValue(new DataView(new ArrayBuffer(length))));
+    }, 'A regular write request to a writable descriptor should succeed.');
+</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-updates-value.html b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-updates-value.html
new file mode 100644
index 0000000..cf4706e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/bluetooth/descriptor/writeValue/write-updates-value.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script>
+'use strict';
+promise_test(() => {
+  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(descriptor => {
+      assert_equals(descriptor.value, null);
+      let textEncoder = new TextEncoder();
+      let newValue = textEncoder.encode('foo');
+      return descriptor.writeValue(newValue).then(() => {
+        assert_array_equals(descriptor.value.buffer, newValue.buffer);
+      })
+    })
+}, 'Succesful read should update descriptor\'s value.');
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-blocklisted.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-blocklisted.js
deleted file mode 100644
index 7c93505..0000000
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-blocklisted.js
+++ /dev/null
@@ -1,24 +0,0 @@
-'use strict';
-promise_test(
-    () => {
-        return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-            .then(
-                () => requestDeviceWithKeyDown(
-                    {filters: [{services: ['health_thermometer']}]}))
-            .then(device => device.gatt.connect())
-            .then(
-                gattServer =>
-                    gattServer.getPrimaryService('health_thermometer'))
-            .then(service => service.getCharacteristic('measurement_interval'))
-            .then(
-                characteristic => characteristic.getDescriptor(
-                    'bad3ec61-3cc3-4954-9702-7977df514114'))
-            .then(descriptor => {
-              return assert_promise_rejects_with_message(
-                  descriptor.CALLS([readValue()]),
-                  new DOMException(
-                      'readValue() called on blocklisted object marked exclude-reads. ' +
-                          'https://goo.gl/4NeimX',
-                      'SecurityError'));
-            })},
-    'Attempt to call FUNCTION_NAME on a blocked descriptor must generate a SecurityError');
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-removed.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-removed.js
index b9de7ed..968115be 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-removed.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/descriptor-is-removed.js
@@ -1,5 +1,6 @@
 'use strict';
 promise_test(() => {
+  let val = new Uint8Array([1]);
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
       .then(
           () => requestDeviceWithKeyDown(
@@ -14,7 +15,7 @@
                    'MissingDescriptorsDisconnectingHealthThermometerAdapter')
             .then(
                 () => assert_promise_rejects_with_message(
-                    descriptor.CALLS([readValue()]),
+                    descriptor.CALLS([readValue()|writeValue(val)]),
                     new DOMException(
                         'GATT Descriptor no longer exists.',
                         'InvalidStateError'),
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
index cc22a117..26c38f6 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-before.js
@@ -19,7 +19,7 @@
             .then(requestDisconnection => requestDisconnection())
             .then(
                 () => assert_promise_rejects_with_message(
-                    user_description.CALLS([readValue()]),
+                    user_description.CALLS([readValue()|writeValue(val)]),
                     new DOMException(
                         'GATT Server is disconnected. Cannot perform GATT operations.',
                         'NetworkError')));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
index a34ac39d..a5f1a72 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-error.js
@@ -20,7 +20,7 @@
                 .then(requestDisconnection => {
                   requestDisconnection();
                   return assert_promise_rejects_with_message(
-                      user_description.CALLS([readValue()]),
+                      user_description.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
                           'GATT Server disconnected while performing a GATT operation.',
                           'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
index 42ff8c7..b06f5146 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-disconnects-during-success.js
@@ -19,7 +19,7 @@
                 .then(requestDisconnection => {
                   requestDisconnection();
                   return assert_promise_rejects_with_message(
-                      user_description.CALLS([readValue()]),
+                      user_description.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
                           'GATT Server disconnected while performing a GATT operation.',
                           'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
index 721e2ce..7a4f3b38 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-error.js
@@ -17,7 +17,7 @@
                 descriptor.characteristic.service.device,
                 'gattserverdisconnected');
             let promise = assert_promise_rejects_with_message(
-                descriptor.CALLS([readValue()]),
+                descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
index 425ec6f..1ffc823 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-device-reconnects-during-success.js
@@ -17,7 +17,7 @@
                 descriptor.characteristic.service.device,
                 'gattserverdisconnected');
             let promise = assert_promise_rejects_with_message(
-                descriptor.CALLS([readValue()]),
+                descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
index f2b7b9d..42c090c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-before.js
@@ -15,7 +15,7 @@
             .then(descriptor => {
               gattServer.disconnect();
               return assert_promise_rejects_with_message(
-                  descriptor.CALLS([readValue()]),
+                  descriptor.CALLS([readValue()|writeValue(val)]),
                   new DOMException(
                       'GATT Server is disconnected. Cannot perform GATT operations.',
                       'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
index 890a3da..e8516733 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-error.js
@@ -15,7 +15,7 @@
                         characteristic.getDescriptor(user_description.name))
                 .then(descriptor => {
                   let promise = assert_promise_rejects_with_message(
-                      descriptor.CALLS([readValue()]),
+                      descriptor.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
                           'GATT Server disconnected while performing a GATT operation.',
                           'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
index 2f764f8..62bb24b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-disconnect-called-during-success.js
@@ -17,7 +17,7 @@
                         characteristic.getDescriptor(user_description.name))
                 .then(descriptor => {
                   let promise = assert_promise_rejects_with_message(
-                      descriptor.CALLS([readValue()]),
+                      descriptor.CALLS([readValue()|writeValue(val)]),
                       new DOMException(
                           'GATT Server disconnected while performing a GATT operation.',
                           'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-fails.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-fails.js
index 741a419..f9121af61 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-fails.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-fails.js
@@ -1,6 +1,7 @@
 'use strict';
 promise_test(
     () => {
+        let val = new Uint8Array([1]);
         return setBluetoothFakeAdapter('FailingGATTOperationsAdapter')
             .then(
                 () => requestDeviceWithKeyDown(
@@ -21,7 +22,7 @@
                                     .then(
                                         descriptor =>
                                             assert_promise_rejects_with_message(
-                                                descriptor.CALLS([readValue()]),
+                                                descriptor.CALLS([readValue()|writeValue(val)]),
                                                 testSpec.error,
                                                 testSpec.testName));
                           });
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
index 1fdf14f..250c016f 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-error.js
@@ -15,7 +15,7 @@
                   characteristic.getDescriptor(user_description.name))
           .then(error_descriptor => {
             promise = assert_promise_rejects_with_message(
-                error_descriptor.CALLS([readValue()]),
+                error_descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
index 0d2ae3c..8dad4b49 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-garbage-collection-ran-during-success.js
@@ -1,6 +1,7 @@
 'use strict';
 promise_test(
     () => {
+      let val = new Uint8Array([1]);
       let promise;
       return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
           .then(
@@ -15,7 +16,7 @@
                   characteristic.getDescriptor(user_description.name))
           .then(descriptor => {
             promise = assert_promise_rejects_with_message(
-                descriptor.CALLS([readValue()]),
+                descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
index e9a30a26..3bf5830 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-error.js
@@ -1,5 +1,6 @@
 promise_test(
     () => {
+      let val = new Uint8Array([1]);
       return setBluetoothFakeAdapter(
                  'GATTOperationFailsAfterReconnectionAdapter')
           .then(
@@ -13,7 +14,7 @@
                   characteristic.getDescriptor(user_description.name))
           .then(descriptor => {
             let promise = assert_promise_rejects_with_message(
-                descriptor.CALLS([readValue()]),
+                descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
index c82a267..54c0d89 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/io-op-reconnect-during-success.js
@@ -14,7 +14,7 @@
                   characteristic.getDescriptor(user_description.name))
           .then(descriptor => {
             let promise = assert_promise_rejects_with_message(
-                descriptor.CALLS([readValue()]),
+                descriptor.CALLS([readValue()|writeValue(val)]),
                 new DOMException(
                     'GATT Server disconnected while performing a GATT operation.',
                     'NetworkError'));
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/service-is-removed.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/service-is-removed.js
index a6058a9..d3486db 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/service-is-removed.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/descriptor/service-is-removed.js
@@ -2,21 +2,19 @@
 promise_test(() => {
   let val = new Uint8Array([1]);
   return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-      .then(
-          () => requestDeviceWithKeyDown(
-              {filters: [{services: ['health_thermometer']}]}))
-      .then(device => device.gatt.connect())
-      .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
-      .then(service => service.getCharacteristic('measurement_interval'))
-      .then(
-          characteristic => characteristic.getDescriptor(user_description.name))
-      .then(descriptor => {
-        return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
-            .then(
-                () => assert_promise_rejects_with_message(
-                    descriptor.CALLS([readValue()]),
-                    new DOMException(
-                        'GATT Service no longer exists.', 'InvalidStateError'),
-                    'Service got removed.'));
-      });
+    .then(() => requestDeviceWithKeyDown({
+      filters: [{services: ['health_thermometer']}]}))
+    .then(device => device.gatt.connect())
+    .then(gattServer => gattServer.getPrimaryService('health_thermometer'))
+    .then(service => service.getCharacteristic('measurement_interval'))
+    .then(characteristic => characteristic.getDescriptor(user_description.name))
+    .then(descriptor => {
+      return setBluetoothFakeAdapter('MissingServiceHeartRateAdapter')
+        .then(() => assert_promise_rejects_with_message(
+          descriptor.CALLS([readValue()|writeValue(val)]),
+          new DOMException(
+            'GATT Service no longer exists.',
+            'InvalidStateError'),
+          'Service got removed.'));
+    });
 }, 'Service gets removed. Reject with InvalidStateError.');
diff --git a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-insertion.html b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-insertion.html
deleted file mode 100644
index 194c845b..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-insertion.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<style>
-body {
-    margin: 0;
-}
-
-.scroller {
-    height: 100px;
-    width: 200px;
-    overflow: scroll;
-}
-
-.padding {
-    width: 400px;
-    height: 500px;
-}
-
-.sticky {
-    width: 25px;
-    height: 25px;
-    position: sticky;
-    top: 20px;
-    left: 50px;
-}
-</style>
-
-<div class="scroller" id="scroller">
-  <div class="padding" id="padding"></div>
-</div>
-
-<div class="scroller" id="scroller2">
-  <div class="padding" id="padding2"></div>
-</div>
-
-<script>
-if (window.internals) {
-  internals.settings.setCSSStickyPositionEnabled(true);
-}
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    scroller.scrollTop = 100;
-    scroller.scrollLeft = 50;
-
-    var sticky = document.createElement('div');
-    sticky.className = 'sticky';
-    scroller.insertBefore(sticky, document.getElementById('padding'));
-
-    assert_equals(sticky.offsetTop, scroller.scrollTop + 20);
-    assert_equals(sticky.offsetLeft, scroller.scrollLeft + 50);
-}, "offsetTop/offsetLeft should be correct for sticky after insertion");
-
-test(function() {
-    var scroller = document.getElementById('scroller2');
-    scroller.scrollTop = 100;
-    scroller.scrollLeft = 50;
-
-    var sticky = document.createElement('div');
-    sticky.className = 'sticky';
-    scroller.insertBefore(sticky, document.getElementById('padding2'));
-
-    assert_equals(sticky.getBoundingClientRect().top, 120);
-    assert_equals(sticky.getBoundingClientRect().left, 50);
-}, "getBoundingClientRect should be correct for sticky after insertion");
-
-
-</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-layout.html b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-layout.html
deleted file mode 100644
index bac326d..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-valid-after-layout.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<style>
-body {
-    margin: 0;
-}
-
-#scroller {
-    height: 100px;
-    width: 200px;
-    overflow: scroll;
-}
-
-.padding {
-    width: 400px;
-    height: 500px;
-}
-
-#sticky {
-    width: 25px;
-    height: 25px;
-    position: sticky;
-    top: 20px;
-    left: 50px;
-}
-</style>
-
-<div id="scroller">
-  <div id="sticky"></div>
-  <div class="padding"></div>
-</div>
-
-<script>
-if (window.internals) {
-  internals.settings.setCSSStickyPositionEnabled(true);
-}
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    scroller.scrollTop = 100;
-    scroller.scrollLeft = 50;
-    scroller.append(document.createElement('div'));
-    assert_equals(sticky.getBoundingClientRect().top, 20);
-    assert_equals(sticky.getBoundingClientRect().left, 50);
-}, "getBoundingClientRect should be correct for sticky after script-caused layout");
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    scroller.scrollTop = 100;
-    scroller.scrollLeft = 50;
-    scroller.append(document.createElement('div'));
-    assert_equals(sticky.offsetTop, 120);
-    assert_equals(sticky.offsetLeft, 100);
-}, "offsetTop/offsetLeft should be correct for sticky after script-caused layout");
-</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-works-with-scroll-apis.html b/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-works-with-scroll-apis.html
deleted file mode 100644
index d06245c9..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/sticky/sticky-position-works-with-scroll-apis.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<style>
-body {
-    margin: 0;
-}
-
-#scroller {
-    height: 100px;
-    width: 200px;
-    overflow-y: scroll;
-}
-
-.paddingBefore {
-    height: 150px;
-}
-
-#sticky {
-    height: 50px;
-    position: sticky;
-    top: 20px;
-}
-
-.paddingAfter {
-    height: 500px;
-}
-</style>
-
-<div id="scroller">
-  <div class="paddingBefore"></div>
-  <div id="writer"></div>
-  <div id="sticky"></div>
-  <div class="paddingAfter"></div>
-</div>
-
-<script>
-if (window.internals) {
-  internals.settings.setCSSStickyPositionEnabled(true);
-}
-
-// These tests currently mimic the behavior of Firefox for the interaction
-// between scrollIntoView() and position:sticky, where the offset location of
-// the sticky element is used to determine how far to scroll. This means that
-// scrollIntoView() may scroll even when the sticky is already 'in view', and
-// attempts to scroll so that the offset position of the sticky is at the top
-// of the screen.
-//
-// TODO(smcgruer): Update tests once http://crbug.com/664246 is resolved.
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    var writer = document.getElementById('writer');
-
-    // Clean the writer.
-    writer.innerHTML = '';
-
-    // With no scroll, the sticky element is outside the scroller viewport.
-    scroller.scrollTop = 0;
-
-    // Deliberately dirty layout to make sure that scrollIntoView() still works.
-    writer.innerHTML = '<div style="height: 50px;"></div>';
-
-    sticky.scrollIntoView();
-    assert_equals(scroller.scrollTop, 200);
-}, "scrollIntoView should scroll when sticky is not visible");
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    var writer = document.getElementById('writer');
-
-    // Clean the writer.
-    writer.innerHTML = '';
-
-    // Scroll so that the sticky element is past the top of the scroller
-    // viewport, and is thus sticking.
-    scroller.scrollTop = 200;
-
-    // Deliberately dirty layout to make sure that scrollIntoView() still works.
-    writer.innerHTML = '<div style="height: 10px;"></div>';
-
-    // See comment above tests for why this shifts by an additional 20 pixels.
-    sticky.scrollIntoView();
-    assert_equals(scroller.scrollTop, 230);
-}, "scrollIntoView should scroll when sticky is already in view");
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    var writer = document.getElementById('writer');
-
-    // Clean the writer.
-    writer.innerHTML = '';
-
-    // With no scroll, the sticky element is outside the scroller viewport.
-    scroller.scrollTop = 0;
-
-    // Deliberately dirty layout to make sure that scrollIntoViewIfNeeded()
-    // still works.
-    writer.innerHTML = '<div style="height: 70px;"></div>';
-
-    sticky.scrollIntoViewIfNeeded();
-    assert_equals(scroller.scrollTop, 195);
-}, "scrollIntoViewIfNeeded should scroll when sticky is not visible");
-
-test(function() {
-    var scroller = document.getElementById('scroller');
-    var sticky = document.getElementById('sticky');
-    var writer = document.getElementById('writer');
-
-    // Clean the writer.
-    writer.innerHTML = '';
-
-    // Scroll so that the sticky element is at the top of the scroller viewport.
-    scroller.scrollTop = 150;
-
-    // Deliberately dirty layout to make sure that scrollIntoViewIfNeeded()
-    // still works.
-    writer.innerHTML = '<div style="height: 20px;"></div>';
-
-    sticky.scrollIntoViewIfNeeded();
-    assert_equals(scroller.scrollTop, 170);
-}, "scrollIntoViewIfNeeded should not scroll when sticky is already in view");
-</script>
diff --git a/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation-expected.txt b/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation-expected.txt
deleted file mode 100644
index 99087ff0..0000000
--- a/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation-expected.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Test Automation of Biquad Filters
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS Output of bandpass filter with frequency automation equals [0,0.27211377024650574,0.17078085243701935,-0.47929149866104126,-0.27997663617134094,0.3924965262413025,0.10922128707170486,-0.3936297297477722,0.0668390765786171,0.40758851170539856,-0.18572545051574707,-0.35690367221832275,0.28689950704574585,0.26041892170906067,-0.3702554702758789,-0.14302822947502136,...] with an element-wise tolerance of 0.0000048429.
-PASS Output of bandpass filter with Q automation equals [0,0.013992002233862877,0.052294112741947174,0.1082303449511528,0.17509238421916962,0.24637210369110107,0.3159724175930023,0.3783925771713257,0.4288841784000397,0.4635751247406006,0.47955939173698425,0.47495153546333313,0.4489057660102844,0.40160006284713745,0.3341873586177826,0.2487161010503769,...] with an element-wise tolerance of 0.0000011062.
-PASS Output of lowshelf filter with gain automation equals [0,0.47850650548934937,1.554081916809082,3.0527422428131104,4.671957969665527,6.188648223876953,7.4877095222473145,8.524102210998535,9.282732963562012,9.756352424621582,9.939204216003418,9.828425407409668,9.427144050598145,8.746537208557129,7.806419372558594,6.634762763977051,...] with an element-wise tolerance of 0.000014306.
-PASS Output of bandpass filter with detune automation equals [0,0.0008340422064065933,0.0014052728656679392,0.0003835858660750091,0.0001248704647878185,0.0012254818575456738,0.0011344455415382981,0.000052847364713670686,0.0004774949047714472,0.0014240697491914034,0.0006955951685085893,-0.000031629722798243165,0.0009216234902851284,0.0013469421537593007,0.0002514156512916088,0.0001623158750589937,...] with an element-wise tolerance of 0.000029535.
-PASS Output of peaking filter with automation of all parameters equals [0,0.9876883625984192,-0.30901700258255005,-0.8910065293312073,0.5877852439880371,0.7071067690849304,-0.80901700258255,-0.45399048924446106,0.9510565400123596,0.15643446147441864,-1,0.15643446147441864,0.9510565400123596,-0.45399048924446106,-0.80901700258255,0.7071067690849304,...] with an element-wise tolerance of 0.00062907.
-PASS Output of bandpass filter with sinusoidal modulation of bandpass center frequency equals [0,0.0018003738950937986,0.00716581242159009,0.015862563624978065,0.027496544644236565,0.04151911661028862,0.05723972246050835,0.07384545356035233,0.09042731672525406,0.10601259768009186,0.11960244923830032,0.13021349906921387,0.13692189753055573,0.13890819251537323,0.13550083339214325,0.12621651589870453,...] with an element-wise tolerance of 0.000039787.
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation.html b/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation.html
index 15a0a93..06476e6 100644
--- a/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation.html
+++ b/third_party/WebKit/LayoutTests/webaudio/BiquadFilter/biquad-automation.html
@@ -2,7 +2,8 @@
 <html>
   <head>
     <title>Biquad Automation Test</title>
-    <script src="../../resources/js-test.js"></script>
+    <script src="../../resources/testharness.js"></script>
+    <script src="../../resources/testharnessreport.js"></script> 
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audio-testing.js"></script>
     <script src="../resources/biquad-filters.js"></script>
@@ -10,9 +11,7 @@
   </head>
   <body>
     <script>
-      description("Test Automation of Biquad Filters");
 
-      window.jsTestIsAsync = true;
 
       // Don't need to run these tests at high sampling rate, so just use a low one to reduce memory
       // usage and complexity.
@@ -363,7 +362,6 @@
 
       // All done!
       audit.defineTask("finish", function (done) {
-        finishJSTest();
         done();
       });
 
diff --git a/third_party/WebKit/Source/core/dom/Element.cpp b/third_party/WebKit/Source/core/dom/Element.cpp
index ff3aeb0..7e63fa9 100644
--- a/third_party/WebKit/Source/core/dom/Element.cpp
+++ b/third_party/WebKit/Source/core/dom/Element.cpp
@@ -429,7 +429,7 @@
 }
 
 void Element::scrollIntoView(bool alignToTop) {
-  ensureCompositingInputsClean();
+  document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
 
   if (!layoutObject())
     return;
@@ -454,7 +454,7 @@
 }
 
 void Element::scrollIntoViewIfNeeded(bool centerIfNeeded) {
-  ensureCompositingInputsClean();
+  document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
 
   if (!layoutObject())
     return;
@@ -634,7 +634,7 @@
 }
 
 int Element::offsetLeft() {
-  ensureCompositingInputsClean();
+  document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
   if (LayoutBoxModelObject* layoutObject = layoutBoxModelObject())
     return adjustLayoutUnitForAbsoluteZoom(
                LayoutUnit(layoutObject->pixelSnappedOffsetLeft(offsetParent())),
@@ -644,7 +644,7 @@
 }
 
 int Element::offsetTop() {
-  ensureCompositingInputsClean();
+  document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
   if (LayoutBoxModelObject* layoutObject = layoutBoxModelObject())
     return adjustLayoutUnitForAbsoluteZoom(
                LayoutUnit(layoutObject->pixelSnappedOffsetTop(offsetParent())),
@@ -1119,7 +1119,7 @@
 }
 
 void Element::clientQuads(Vector<FloatQuad>& quads) {
-  ensureCompositingInputsClean();
+  document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(this);
 
   LayoutObject* elementLayoutObject = layoutObject();
   if (!elementLayoutObject)
@@ -4120,19 +4120,6 @@
   activityLogger->logEvent("blinkSetAttribute", argv.size(), argv.data());
 }
 
-void Element::ensureCompositingInputsClean() {
-  if (!inActiveDocument())
-    return;
-
-  // The call to updateLifecycleToCompositingCleanPlusScrolling| below would
-  // also run layout for us if we omitted this call. However we do not want to
-  // include pending style sheets when doing the layout, hence this call.
-  document().updateStyleAndLayoutIgnorePendingStylesheets();
-
-  if (FrameView* view = document().view())
-    view->updateLifecycleToCompositingCleanPlusScrolling();
-}
-
 DEFINE_TRACE(Element) {
   if (hasRareData())
     visitor->trace(elementRareData());
diff --git a/third_party/WebKit/Source/core/dom/Element.h b/third_party/WebKit/Source/core/dom/Element.h
index aa1466f..8a26e38 100644
--- a/third_party/WebKit/Source/core/dom/Element.h
+++ b/third_party/WebKit/Source/core/dom/Element.h
@@ -801,11 +801,6 @@
 
   virtual void parserDidSetAttributes() {}
 
-  // Helper method which ensures that compositing inputs have been cleaned.
-  // Cleaning the compositing inputs is required when computing the location of
-  // position:sticky elements or their descendants.
-  void ensureCompositingInputsClean();
-
  private:
   void scrollLayoutBoxBy(const ScrollToOptions&);
   void scrollLayoutBoxTo(const ScrollToOptions&);
diff --git a/third_party/WebKit/Source/core/dom/ElementTest.cpp b/third_party/WebKit/Source/core/dom/ElementTest.cpp
index 40de9e4c..30064c6 100644
--- a/third_party/WebKit/Source/core/dom/ElementTest.cpp
+++ b/third_party/WebKit/Source/core/dom/ElementTest.cpp
@@ -7,8 +7,6 @@
 #include "core/dom/Document.h"
 #include "core/frame/FrameView.h"
 #include "core/html/HTMLHtmlElement.h"
-#include "core/layout/LayoutBoxModelObject.h"
-#include "core/paint/PaintLayer.h"
 #include "core/testing/DummyPageHolder.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include <memory>
@@ -25,131 +23,4 @@
       << "<html> with designMode=on should be focusable.";
 }
 
-// Verifies that calling getBoundingClientRect cleans compositor inputs.
-// Cleaning the compositor inputs is required so that position:sticky elements
-// and their descendants have the correct location.
-TEST(ElementTest, GetBoundingClientRectUpdatesCompositorInputs) {
-  std::unique_ptr<DummyPageHolder> pageHolder = DummyPageHolder::create();
-  Document& document = pageHolder->document();
-
-  document.view()->updateAllLifecyclePhases();
-  EXPECT_EQ(DocumentLifecycle::PaintClean, document.lifecycle().state());
-
-  document.body()->setInnerHTML("<div id='test'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  document.body()->getBoundingClientRect();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-}
-
-// Verifies that calling scrollIntoView* cleans compositor inputs. Cleaning the
-// compositor inputs is required so that position:sticky elements and their
-// descendants have the correct location.
-TEST(ElementTest, ScrollIntoViewUpdatesCompositorInputs) {
-  std::unique_ptr<DummyPageHolder> pageHolder = DummyPageHolder::create();
-  Document& document = pageHolder->document();
-
-  document.view()->updateAllLifecyclePhases();
-  EXPECT_EQ(DocumentLifecycle::PaintClean, document.lifecycle().state());
-
-  document.body()->setInnerHTML("<div id='test'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  document.body()->scrollIntoView();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-
-  document.body()->setInnerHTML("<div id='test2'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  document.body()->scrollIntoViewIfNeeded();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-}
-
-// Verifies that calling offsetTop/offsetLeft cleans compositor inputs.
-// Cleaning the compositor inputs is required so that position:sticky elements
-// and their descendants have the correct location.
-TEST(ElementTest, OffsetTopAndLeftUpdateCompositorInputs) {
-  std::unique_ptr<DummyPageHolder> pageHolder = DummyPageHolder::create();
-  Document& document = pageHolder->document();
-
-  document.view()->updateAllLifecyclePhases();
-  EXPECT_EQ(DocumentLifecycle::PaintClean, document.lifecycle().state());
-
-  document.body()->setInnerHTML("<div id='test'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  document.body()->offsetTop();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-
-  document.body()->setInnerHTML("<div id='test2'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  document.body()->offsetLeft();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-}
-
-TEST(ElementTest, OffsetTopAndLeftCorrectForStickyElementsAfterInsertion) {
-  std::unique_ptr<DummyPageHolder> pageHolder = DummyPageHolder::create();
-  Document& document = pageHolder->document();
-
-  document.body()->setInnerHTML(
-      "<style>body { margin: 0 }"
-      "#scroller { overflow: scroll; height: 100px; width: 100px; }"
-      "#sticky { height: 25px; position: sticky; top: 0; left: 25px; }"
-      "#padding { height: 500px; width: 300px; }</style>"
-      "<div id='scroller'><div id='writer'></div><div id='sticky'></div>"
-      "<div id='padding'></div></div>");
-  document.view()->updateAllLifecyclePhases();
-
-  Element* scroller = document.getElementById("scroller");
-  Element* writer = document.getElementById("writer");
-  Element* sticky = document.getElementById("sticky");
-
-  ASSERT_TRUE(scroller);
-  ASSERT_TRUE(writer);
-  ASSERT_TRUE(sticky);
-
-  scroller->scrollTo(0, 200.0);
-
-  // After scrolling, the position:sticky element should be offset to stay at
-  // the top of the viewport.
-  EXPECT_EQ(scroller->scrollTop(), sticky->offsetTop());
-
-  // Insert a new <div> above the sticky. This will dirty layout.
-  writer->setInnerHTML("<div style='height: 100px;'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  // Querying the new offset of the sticky element should now cause both layout
-  // and compositing inputs to be clean and should return the correct offset.
-  int offsetTop = sticky->offsetTop();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-  EXPECT_FALSE(
-      sticky->layoutBoxModelObject()->layer()->needsCompositingInputsUpdate());
-  EXPECT_EQ(scroller->scrollTop(), offsetTop);
-
-  scroller->scrollTo(50.0, 0);
-
-  // After scrolling, the position:sticky element should be offset to stay 25
-  // pixels from the left of the viewport.
-  EXPECT_EQ(scroller->scrollLeft() + 25, sticky->offsetLeft());
-
-  // Insert a new <div> above the sticky. This will dirty layout.
-  writer->setInnerHTML("<div style='width: 700px;'></div>");
-  EXPECT_EQ(DocumentLifecycle::VisualUpdatePending,
-            document.lifecycle().state());
-
-  // Again getting the offset should cause layout and compositing to be clean.
-  int offsetLeft = sticky->offsetLeft();
-  EXPECT_EQ(DocumentLifecycle::CompositingClean, document.lifecycle().state());
-  EXPECT_FALSE(
-      sticky->layoutBoxModelObject()->layer()->needsCompositingInputsUpdate());
-  EXPECT_EQ(scroller->scrollLeft() + 25, offsetLeft);
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/html/HTMLElement.cpp b/third_party/WebKit/Source/core/html/HTMLElement.cpp
index 1ca5128c..deaffb10 100644
--- a/third_party/WebKit/Source/core/html/HTMLElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLElement.cpp
@@ -1161,7 +1161,6 @@
 }
 
 int HTMLElement::offsetLeftForBinding() {
-  ensureCompositingInputsClean();
   Element* offsetParent = unclosedOffsetParent();
   if (LayoutBoxModelObject* layoutObject = layoutBoxModelObject())
     return adjustLayoutUnitForAbsoluteZoom(
@@ -1172,7 +1171,6 @@
 }
 
 int HTMLElement::offsetTopForBinding() {
-  ensureCompositingInputsClean();
   Element* offsetParent = unclosedOffsetParent();
   if (LayoutBoxModelObject* layoutObject = layoutBoxModelObject())
     return adjustLayoutUnitForAbsoluteZoom(
diff --git a/third_party/WebKit/Source/core/layout/LayoutCounter.cpp b/third_party/WebKit/Source/core/layout/LayoutCounter.cpp
index c3319605..2213a17 100644
--- a/third_party/WebKit/Source/core/layout/LayoutCounter.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutCounter.cpp
@@ -426,7 +426,7 @@
 }
 
 LayoutCounter::LayoutCounter(Document* node, const CounterContent& counter)
-    : LayoutText(node, StringImpl::empty()),
+    : LayoutText(node, StringImpl::empty),
       m_counter(counter),
       m_counterNode(nullptr),
       m_nextForSameCounter(nullptr) {
diff --git a/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp b/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
index e6f24dd..05cace5f 100644
--- a/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutMenuList.cpp
@@ -70,7 +70,7 @@
   ASSERT(!firstChild());
   m_innerBlock = createAnonymousBlock();
 
-  m_buttonText = new LayoutText(&document(), StringImpl::empty());
+  m_buttonText = new LayoutText(&document(), StringImpl::empty);
   // We need to set the text explicitly though it was specified in the
   // constructor because LayoutText doesn't refer to the text
   // specified in the constructor in a case of re-transforming.
diff --git a/third_party/WebKit/Source/core/layout/LayoutWordBreak.cpp b/third_party/WebKit/Source/core/layout/LayoutWordBreak.cpp
index 64c2c08..574b164 100644
--- a/third_party/WebKit/Source/core/layout/LayoutWordBreak.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutWordBreak.cpp
@@ -31,7 +31,7 @@
 namespace blink {
 
 LayoutWordBreak::LayoutWordBreak(HTMLElement* element)
-    : LayoutText(element, StringImpl::empty()) {}
+    : LayoutText(element, StringImpl::empty) {}
 
 bool LayoutWordBreak::isWordBreak() const {
   return true;
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
index 96d92ae..8e915fe 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
@@ -829,10 +829,6 @@
 
   DisableCompositingQueryAsserts disabler;
   positionOverflowControls();
-
-  // Layout of a scrollable area, or any of its descendants (sticky or
-  // otherwise), invalidates the cached sticky constraints.
-  invalidateAllStickyConstraints();
 }
 
 void PaintLayerScrollableArea::clampScrollOffsetAfterOverflowChange() {
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
index c633a7f..d0cb1b8 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableAreaTest.cpp
@@ -28,7 +28,7 @@
   }
 };
 
-}  // namespace
+}  // namespace {
 
 class PaintLayerScrollableAreaTest : public RenderingTest {
  public:
@@ -548,31 +548,4 @@
       .Times(0);
   scrollableArea->setScrollOffset(ScrollOffset(2, 2), ProgrammaticScroll);
 }
-
-TEST_F(PaintLayerScrollableAreaTest,
-       StickyPositionConstraintsInvalidatedByLayout) {
-  setBodyInnerHTML(
-      "<style>#scroller { overflow-y: scroll; height: 100px; }"
-      "#sticky { height: 25px; position: sticky; top: 0; }"
-      "#padding { height: 500px; }</style>"
-      "<div id='scroller'><div id='sticky'></div><div id='padding'></div>"
-      "</div>");
-
-  LayoutBoxModelObject* scroller =
-      toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
-  LayoutBoxModelObject* sticky =
-      toLayoutBoxModelObject(getLayoutObjectByElementId("sticky"));
-
-  EXPECT_FALSE(sticky->layer()->needsCompositingInputsUpdate());
-
-  PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
-  ASSERT_TRUE(scrollableArea);
-
-  // Fake layout.
-  scrollableArea->updateAfterLayout();
-
-  EXPECT_TRUE(sticky->layer()->needsCompositingInputsUpdate());
-  EXPECT_EQ(0u, scrollableArea->stickyConstraintsMap().size());
 }
-
-}  // namespace blink
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
index 8b0ca46..9bb73d6 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.cpp
@@ -7,7 +7,6 @@
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "core/dom/DOMException.h"
-#include "modules/bluetooth/Bluetooth.h"
 #include "modules/bluetooth/BluetoothError.h"
 #include "modules/bluetooth/BluetoothRemoteGATTService.h"
 #include "modules/bluetooth/BluetoothRemoteGATTUtils.h"
@@ -74,10 +73,7 @@
   ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
   ScriptPromise promise = resolver->promise();
   getGatt()->AddToActiveAlgorithms(resolver);
-
-  mojom::blink::WebBluetoothService* service =
-      m_characteristic->m_device->bluetooth()->service();
-  service->RemoteDescriptorReadValue(
+  getService()->RemoteDescriptorReadValue(
       m_descriptor->instance_id,
       convertToBaseCallback(
           WTF::bind(&BluetoothRemoteGATTDescriptor::ReadValueCallback,
@@ -86,15 +82,73 @@
   return promise;
 }
 
+void BluetoothRemoteGATTDescriptor::WriteValueCallback(
+    ScriptPromiseResolver* resolver,
+    const Vector<uint8_t>& value,
+    mojom::blink::WebBluetoothResult result) {
+  if (!resolver->getExecutionContext() ||
+      resolver->getExecutionContext()->isContextDestroyed())
+    return;
+
+  // If the resolver is not in the set of ActiveAlgorithms then the frame
+  // disconnected so we reject.
+  if (!getGatt()->RemoveFromActiveAlgorithms(resolver)) {
+    resolver->reject(BluetoothRemoteGATTUtils::CreateDOMException(
+        BluetoothRemoteGATTUtils::ExceptionType::kGATTServerDisconnected));
+    return;
+  }
+
+  if (result == mojom::blink::WebBluetoothResult::SUCCESS) {
+    m_value = BluetoothRemoteGATTUtils::ConvertWTFVectorToDataView(value);
+    resolver->resolve();
+  } else {
+    resolver->reject(BluetoothError::take(resolver, result));
+  }
+}
+
 ScriptPromise BluetoothRemoteGATTDescriptor::writeValue(
     ScriptState* scriptState,
     const DOMArrayPiece& value) {
-  // TODO(668838): Implement WebBluetooth descriptor.writeValue()
-  return ScriptPromise::rejectWithDOMException(
-      scriptState,
-      DOMException::create(NotSupportedError,
-                           "descriptor writeValue is not implemented "
-                           "yet. See https://goo.gl/J6ASzs"));
+  if (!getGatt()->connected()) {
+    return ScriptPromise::rejectWithDOMException(
+        scriptState,
+        BluetoothRemoteGATTUtils::CreateDOMException(
+            BluetoothRemoteGATTUtils::ExceptionType::kGATTServerNotConnected));
+  }
+
+  if (!getGatt()->device()->isValidDescriptor(m_descriptor->instance_id)) {
+    return ScriptPromise::rejectWithDOMException(
+        scriptState,
+        BluetoothRemoteGATTUtils::CreateDOMException(
+            BluetoothRemoteGATTUtils::ExceptionType::kInvalidDescriptor));
+  }
+
+  // Partial implementation of writeValue algorithm:
+  // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattdescriptor-writevalue
+
+  // If bytes is more than 512 bytes long (the maximum length of an attribute
+  // value, per Long Attribute Values) return a promise rejected with an
+  // InvalidModificationError and abort.
+  if (value.byteLength() > 512) {
+    return ScriptPromise::rejectWithDOMException(
+        scriptState, DOMException::create(InvalidModificationError,
+                                          "Value can't exceed 512 bytes."));
+  }
+
+  // Let valueVector be a copy of the bytes held by value.
+  Vector<uint8_t> valueVector;
+  valueVector.append(value.bytes(), value.byteLength());
+
+  ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
+  ScriptPromise promise = resolver->promise();
+  getGatt()->AddToActiveAlgorithms(resolver);
+  getService()->RemoteDescriptorWriteValue(
+      m_descriptor->instance_id, valueVector,
+      convertToBaseCallback(WTF::bind(
+          &BluetoothRemoteGATTDescriptor::WriteValueCallback,
+          wrapPersistent(this), wrapPersistent(resolver), valueVector)));
+
+  return promise;
 }
 
 DEFINE_TRACE(BluetoothRemoteGATTDescriptor) {
diff --git a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.h b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.h
index b491c1d..175db616 100644
--- a/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.h
+++ b/third_party/WebKit/Source/modules/bluetooth/BluetoothRemoteGATTDescriptor.h
@@ -9,6 +9,7 @@
 #include "core/dom/DOMArrayPiece.h"
 #include "core/dom/DOMDataView.h"
 #include "modules/EventTargetModules.h"
+#include "modules/bluetooth/Bluetooth.h"
 #include "modules/bluetooth/BluetoothRemoteGATTCharacteristic.h"
 #include "modules/bluetooth/BluetoothRemoteGATTService.h"
 #include "platform/heap/Handle.h"
@@ -54,11 +55,18 @@
   friend class DescriptorReadValueCallback;
 
   BluetoothRemoteGATTServer* getGatt() { return m_characteristic->getGatt(); }
+  mojom::blink::WebBluetoothService* getService() {
+    return m_characteristic->m_device->bluetooth()->service();
+  }
 
   void ReadValueCallback(ScriptPromiseResolver*,
                          mojom::blink::WebBluetoothResult,
                          const Optional<Vector<uint8_t>>&);
 
+  void WriteValueCallback(ScriptPromiseResolver*,
+                          const Vector<uint8_t>&,
+                          mojom::blink::WebBluetoothResult);
+
   mojom::blink::WebBluetoothRemoteGATTDescriptorPtr m_descriptor;
   Member<BluetoothRemoteGATTCharacteristic> m_characteristic;
   Member<DOMDataView> m_value;
diff --git a/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp b/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
index c29f840..076b64f 100644
--- a/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
+++ b/third_party/WebKit/Source/modules/vr/NavigatorVR.cpp
@@ -9,6 +9,7 @@
 #include "core/dom/Document.h"
 #include "core/dom/DocumentUserGestureToken.h"
 #include "core/dom/ExceptionCode.h"
+#include "core/dom/Fullscreen.h"
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/Navigator.h"
@@ -132,7 +133,11 @@
 
 void NavigatorVR::didAddEventListener(LocalDOMWindow* window,
                                       const AtomicString& eventType) {
-  if (eventType == EventTypeNames::vrdisplayactivate) {
+  // TODO(mthiesse): Remove fullscreen requirement for presentation. See
+  // crbug.com/687369
+  if (eventType == EventTypeNames::vrdisplayactivate &&
+      supplementable()->frame() && supplementable()->frame()->document() &&
+      Fullscreen::fullscreenEnabled(*supplementable()->frame()->document())) {
     controller()->setListeningForActivate(true);
     m_listeningForActivate = true;
   } else if (eventType == EventTypeNames::vrdisplayconnect) {
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
index 6be80f1..4810e4f 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
@@ -238,6 +238,17 @@
     return promise;
   }
 
+  // TODO(mthiesse): Remove fullscreen requirement for presentation. See
+  // crbug.com/687369
+  Document* doc = this->document();
+  if (!doc || !Fullscreen::fullscreenEnabled(*doc)) {
+    DOMException* exception =
+        DOMException::create(InvalidStateError, "Fullscreen is not enabled.");
+    resolver->reject(exception);
+    ReportPresentationResult(PresentationResult::FullscreenNotEnabled);
+    return promise;
+  }
+
   // A valid number of layers must be provided in order to present.
   if (layers.size() == 0 || layers.size() > m_capabilities->maxLayers()) {
     forceExitPresent();
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.h b/third_party/WebKit/Source/modules/vr/VRDisplay.h
index c756314..7a3d101 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.h
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.h
@@ -192,6 +192,7 @@
   InvalidLayerBounds = 10,
   ServiceInactive = 11,
   RequestDenied = 12,
+  FullscreenNotEnabled = 13,
   PresentationResultMax,  // Must be last member of enum.
 };
 
diff --git a/third_party/WebKit/Source/platform/weborigin/KURL.cpp b/third_party/WebKit/Source/platform/weborigin/KURL.cpp
index f34a5bc..6df46cf 100644
--- a/third_party/WebKit/Source/platform/weborigin/KURL.cpp
+++ b/third_party/WebKit/Source/platform/weborigin/KURL.cpp
@@ -836,7 +836,7 @@
 }
 
 StringView KURL::stringViewForInvalidComponent() const {
-  return m_string.isNull() ? StringView() : StringView(StringImpl::empty());
+  return m_string.isNull() ? StringView() : StringView(StringImpl::empty);
 }
 
 StringView KURL::componentStringView(const url::Component& component) const {
diff --git a/third_party/WebKit/Source/wtf/ThreadingPthreads.cpp b/third_party/WebKit/Source/wtf/ThreadingPthreads.cpp
index 886e661..6120cb6 100644
--- a/third_party/WebKit/Source/wtf/ThreadingPthreads.cpp
+++ b/third_party/WebKit/Source/wtf/ThreadingPthreads.cpp
@@ -81,10 +81,6 @@
   // This should only be called once.
   DCHECK(!atomicallyInitializedStaticMutex);
 
-  // StringImpl::empty() does not construct its static string in a threadsafe
-  // fashion, so ensure it has been initialized from here.
-  StringImpl::empty();
-  StringImpl::empty16Bit();
   atomicallyInitializedStaticMutex = new Mutex;
   wtfThreadData();
   initializeDates();
diff --git a/third_party/WebKit/Source/wtf/ThreadingWin.cpp b/third_party/WebKit/Source/wtf/ThreadingWin.cpp
index 8808450..3c21d2a 100644
--- a/third_party/WebKit/Source/wtf/ThreadingWin.cpp
+++ b/third_party/WebKit/Source/wtf/ThreadingWin.cpp
@@ -148,10 +148,6 @@
   // This should only be called once.
   DCHECK(!atomicallyInitializedStaticMutex);
 
-  // StringImpl::empty() does not construct its static string in a threadsafe
-  // fashion, so ensure it has been initialized from here.
-  StringImpl::empty();
-  StringImpl::empty16Bit();
   atomicallyInitializedStaticMutex = new Mutex;
   wtfThreadData();
   initializeDates();
diff --git a/third_party/WebKit/Source/wtf/text/AtomicStringTable.cpp b/third_party/WebKit/Source/wtf/text/AtomicStringTable.cpp
index 7ca6f715..bc40b4d 100644
--- a/third_party/WebKit/Source/wtf/text/AtomicStringTable.cpp
+++ b/third_party/WebKit/Source/wtf/text/AtomicStringTable.cpp
@@ -148,7 +148,7 @@
     return nullptr;
 
   if (!length)
-    return StringImpl::empty();
+    return StringImpl::empty;
 
   UCharBuffer buffer = {s, length};
   return addToStringTable<UCharBuffer, UCharBufferTranslator>(buffer);
@@ -178,7 +178,7 @@
     return nullptr;
 
   if (!length)
-    return StringImpl::empty();
+    return StringImpl::empty;
 
   LCharBuffer buffer = {s, length};
   return addToStringTable<LCharBuffer, LCharBufferTranslator>(buffer);
@@ -186,7 +186,7 @@
 
 StringImpl* AtomicStringTable::add(StringImpl* string) {
   if (!string->length())
-    return StringImpl::empty();
+    return StringImpl::empty;
 
   StringImpl* result = *m_table.insert(string).storedValue;
 
diff --git a/third_party/WebKit/Source/wtf/text/StringImpl.cpp b/third_party/WebKit/Source/wtf/text/StringImpl.cpp
index 6654e30..c784115 100644
--- a/third_party/WebKit/Source/wtf/text/StringImpl.cpp
+++ b/third_party/WebKit/Source/wtf/text/StringImpl.cpp
@@ -28,6 +28,7 @@
 #include "wtf/DynamicAnnotations.h"
 #include "wtf/LeakAnnotations.h"
 #include "wtf/PtrUtil.h"
+#include "wtf/StaticConstructors.h"
 #include "wtf/StdLibExtras.h"
 #include "wtf/allocator/Partitions.h"
 #include "wtf/text/AtomicString.h"
@@ -344,7 +345,7 @@
                                                        LChar*& data) {
   if (!length) {
     data = 0;
-    return empty();
+    return empty;
   }
 
   // Allocate a single buffer large enough to contain the StringImpl
@@ -361,7 +362,7 @@
                                                        UChar*& data) {
   if (!length) {
     data = 0;
-    return empty();
+    return empty;
   }
 
   // Allocate a single buffer large enough to contain the StringImpl
@@ -397,6 +398,22 @@
 
 unsigned StringImpl::m_highestStaticStringLength = 0;
 
+DEFINE_GLOBAL(StringImpl, globalEmpty);
+DEFINE_GLOBAL(StringImpl, globalEmpty16Bit);
+// Callers need the global empty strings to be non-const.
+StringImpl* StringImpl::empty = const_cast<StringImpl*>(&globalEmpty);
+StringImpl* StringImpl::empty16Bit = const_cast<StringImpl*>(&globalEmpty16Bit);
+void StringImpl::initStatics() {
+  new ((void*)empty) StringImpl(ConstructEmptyString);
+  new ((void*)empty16Bit) StringImpl(ConstructEmptyString16Bit);
+  WTF_ANNOTATE_BENIGN_RACE(StringImpl::empty,
+                           "Benign race on the reference counter of a static "
+                           "string created by StringImpl::empty");
+  WTF_ANNOTATE_BENIGN_RACE(StringImpl::empty16Bit,
+                           "Benign race on the reference counter of a static "
+                           "string created by StringImpl::empty16Bit");
+}
+
 StringImpl* StringImpl::createStatic(const char* string,
                                      unsigned length,
                                      unsigned hash) {
@@ -451,7 +468,7 @@
 PassRefPtr<StringImpl> StringImpl::create(const UChar* characters,
                                           unsigned length) {
   if (!characters || !length)
-    return empty();
+    return empty;
 
   UChar* data;
   RefPtr<StringImpl> string = createUninitialized(length, data);
@@ -462,7 +479,7 @@
 PassRefPtr<StringImpl> StringImpl::create(const LChar* characters,
                                           unsigned length) {
   if (!characters || !length)
-    return empty();
+    return empty;
 
   LChar* data;
   RefPtr<StringImpl> string = createUninitialized(length, data);
@@ -473,7 +490,7 @@
 PassRefPtr<StringImpl> StringImpl::create8BitIfPossible(const UChar* characters,
                                                         unsigned length) {
   if (!characters || !length)
-    return empty();
+    return empty;
 
   LChar* data;
   RefPtr<StringImpl> string = createUninitialized(length, data);
@@ -489,7 +506,7 @@
 
 PassRefPtr<StringImpl> StringImpl::create(const LChar* string) {
   if (!string)
-    return empty();
+    return empty;
   size_t length = strlen(reinterpret_cast<const char*>(string));
   RELEASE_ASSERT(length <= numeric_limits<unsigned>::max());
   return create(string, length);
@@ -520,7 +537,7 @@
 PassRefPtr<StringImpl> StringImpl::substring(unsigned start,
                                              unsigned length) const {
   if (start >= m_length)
-    return empty();
+    return empty;
   unsigned maxLength = m_length - start;
   if (length >= maxLength) {
     // PassRefPtr has trouble dealing with const arguments. It should be updated
@@ -959,7 +976,7 @@
 inline PassRefPtr<StringImpl> StringImpl::stripMatchedCharacters(
     UCharPredicate predicate) {
   if (!m_length)
-    return empty();
+    return empty;
 
   unsigned start = 0;
   unsigned end = m_length - 1;
@@ -971,7 +988,7 @@
 
   // only white space
   if (start > end)
-    return empty();
+    return empty;
 
   // skip white space from end
   while (end && predicate(is8Bit() ? characters8()[end] : characters16()[end]))
diff --git a/third_party/WebKit/Source/wtf/text/StringImpl.h b/third_party/WebKit/Source/wtf/text/StringImpl.h
index 77dfcc3..afdb6af 100644
--- a/third_party/WebKit/Source/wtf/text/StringImpl.h
+++ b/third_party/WebKit/Source/wtf/text/StringImpl.h
@@ -205,8 +205,13 @@
         m_isStatic(true) {}
 
  public:
+  static StringImpl* empty;
+  static StringImpl* empty16Bit;
+
   ~StringImpl();
 
+  static void initStatics();
+
   static StringImpl* createStatic(const char* string,
                                   unsigned length,
                                   unsigned hash);
@@ -324,9 +329,6 @@
       destroyIfNotStatic();
   }
 
-  static StringImpl* empty();
-  static StringImpl* empty16Bit();
-
   // FIXME: Does this really belong in StringImpl?
   template <typename T>
   static void copyChars(T* destination,
diff --git a/third_party/WebKit/Source/wtf/text/StringMac.mm b/third_party/WebKit/Source/wtf/text/StringMac.mm
index 1311cab..cb410b2 100644
--- a/third_party/WebKit/Source/wtf/text/StringMac.mm
+++ b/third_party/WebKit/Source/wtf/text/StringMac.mm
@@ -30,7 +30,7 @@
 
   CFIndex size = CFStringGetLength(reinterpret_cast<CFStringRef>(str));
   if (size == 0)
-    m_impl = StringImpl::empty();
+    m_impl = StringImpl::empty;
   else {
     Vector<LChar, 1024> lcharBuffer(size);
     CFIndex usedBufLen;
diff --git a/third_party/WebKit/Source/wtf/text/StringStatics.cpp b/third_party/WebKit/Source/wtf/text/StringStatics.cpp
index 2bb0dbe..ff7910a 100644
--- a/third_party/WebKit/Source/wtf/text/StringStatics.cpp
+++ b/third_party/WebKit/Source/wtf/text/StringStatics.cpp
@@ -32,22 +32,6 @@
 
 namespace WTF {
 
-StringImpl* StringImpl::empty() {
-  DEFINE_STATIC_LOCAL(StringImpl, emptyString, (ConstructEmptyString));
-  WTF_ANNOTATE_BENIGN_RACE(&emptyString,
-                           "Benign race on the reference counter of a static "
-                           "string created by StringImpl::empty");
-  return &emptyString;
-}
-
-StringImpl* StringImpl::empty16Bit() {
-  DEFINE_STATIC_LOCAL(StringImpl, emptyString, (ConstructEmptyString16Bit));
-  WTF_ANNOTATE_BENIGN_RACE(&emptyString,
-                           "Benign race on the reference counter of a static "
-                           "string created by StringImpl::empty16Bit");
-  return &emptyString;
-}
-
 WTF_EXPORT DEFINE_GLOBAL(AtomicString, nullAtom);
 WTF_EXPORT DEFINE_GLOBAL(AtomicString, emptyAtom);
 WTF_EXPORT DEFINE_GLOBAL(AtomicString, starAtom);
@@ -89,6 +73,8 @@
 void StringStatics::init() {
   DCHECK(isMainThread());
 
+  StringImpl::initStatics();
+
   // FIXME: These should be allocated at compile time.
   new (NotNull, (void*)&starAtom) AtomicString("*");
   new (NotNull, (void*)&xmlAtom) AtomicString(addStaticASCIILiteral("xml"));
diff --git a/third_party/WebKit/Source/wtf/text/StringView.h b/third_party/WebKit/Source/wtf/text/StringView.h
index ec078f21..bd22113 100644
--- a/third_party/WebKit/Source/wtf/text/StringView.h
+++ b/third_party/WebKit/Source/wtf/text/StringView.h
@@ -68,7 +68,7 @@
 
   // From a literal string or LChar buffer:
   StringView(const LChar* chars, unsigned length)
-      : m_impl(StringImpl::empty()), m_characters8(chars), m_length(length) {}
+      : m_impl(StringImpl::empty), m_characters8(chars), m_length(length) {}
   StringView(const char* chars, unsigned length)
       : StringView(reinterpret_cast<const LChar*>(chars), length) {}
   StringView(const LChar* chars)
@@ -79,7 +79,7 @@
 
   // From a wide literal string or UChar buffer.
   StringView(const UChar* chars, unsigned length)
-      : m_impl(StringImpl::empty16Bit()),
+      : m_impl(StringImpl::empty16Bit),
         m_characters16(chars),
         m_length(length) {}
   StringView(const UChar* chars);
@@ -203,7 +203,7 @@
 inline void StringView::clear() {
   m_length = 0;
   m_bytes = nullptr;
-  m_impl = StringImpl::empty();  // mark as 8 bit.
+  m_impl = StringImpl::empty;  // mark as 8 bit.
 }
 
 inline void StringView::set(const StringImpl& impl,
diff --git a/third_party/WebKit/Source/wtf/text/WTFString.cpp b/third_party/WebKit/Source/wtf/text/WTFString.cpp
index 22c6d0b..95c4159 100644
--- a/third_party/WebKit/Source/wtf/text/WTFString.cpp
+++ b/third_party/WebKit/Source/wtf/text/WTFString.cpp
@@ -251,7 +251,7 @@
     m_impl =
         make16BitFrom8BitSource(m_impl->characters8(), length).releaseImpl();
   else
-    m_impl = StringImpl::empty16Bit();
+    m_impl = StringImpl::empty16Bit;
 }
 
 void String::truncate(unsigned length) {
@@ -776,12 +776,12 @@
 }
 
 const String& emptyString() {
-  DEFINE_STATIC_LOCAL(String, emptyString, (StringImpl::empty()));
+  DEFINE_STATIC_LOCAL(String, emptyString, (StringImpl::empty));
   return emptyString;
 }
 
 const String& emptyString16Bit() {
-  DEFINE_STATIC_LOCAL(String, emptyString, (StringImpl::empty16Bit()));
+  DEFINE_STATIC_LOCAL(String, emptyString, (StringImpl::empty16Bit));
   return emptyString;
 }
 
diff --git a/third_party/WebKit/Source/wtf/text/WTFString.h b/third_party/WebKit/Source/wtf/text/WTFString.h
index a6466448..80508ad1 100644
--- a/third_party/WebKit/Source/wtf/text/WTFString.h
+++ b/third_party/WebKit/Source/wtf/text/WTFString.h
@@ -104,7 +104,7 @@
   template <typename CharType>
   static String adopt(StringBuffer<CharType>& buffer) {
     if (!buffer.length())
-      return StringImpl::empty();
+      return StringImpl::empty;
     return String(buffer.release());
   }
 
@@ -489,7 +489,7 @@
 template <size_t inlineCapacity>
 String::String(const Vector<UChar, inlineCapacity>& vector)
     : m_impl(vector.size() ? StringImpl::create(vector.data(), vector.size())
-                           : StringImpl::empty()) {}
+                           : StringImpl::empty) {}
 
 template <>
 inline const LChar* String::getCharacters<LChar>() const {
diff --git a/third_party/WebKit/Source/wtf/text/WTFStringTest.cpp b/third_party/WebKit/Source/wtf/text/WTFStringTest.cpp
index 0f6c640..b8adf49 100644
--- a/third_party/WebKit/Source/wtf/text/WTFStringTest.cpp
+++ b/third_party/WebKit/Source/wtf/text/WTFStringTest.cpp
@@ -445,14 +445,14 @@
   EXPECT_FALSE(string16.is8Bit());
   EXPECT_EQ("16bit", string16);
 
-  String empty8(StringImpl::empty());
+  String empty8(StringImpl::empty);
   EXPECT_TRUE(empty8.is8Bit());
   empty8.ensure16Bit();
   EXPECT_FALSE(empty8.is8Bit());
   EXPECT_TRUE(empty8.isEmpty());
   EXPECT_FALSE(empty8.isNull());
 
-  String empty16(StringImpl::empty16Bit());
+  String empty16(StringImpl::empty16Bit);
   EXPECT_FALSE(empty16.is8Bit());
   empty16.ensure16Bit();
   EXPECT_FALSE(empty16.is8Bit());
diff --git a/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom b/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
index ed1adc8..43ac52f4 100644
--- a/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
+++ b/third_party/WebKit/public/platform/modules/bluetooth/web_bluetooth.mojom
@@ -250,6 +250,14 @@
     string descriptor_instance_id) => (
     WebBluetoothResult result,
     array<uint8>? value);
+
+  // Writes a value to the descriptor identified by
+  // |descriptor_instance_id|. The callback is run with
+  // WebBluetoothResult::SUCCESS if the value was successfully
+  // written.
+  RemoteDescriptorWriteValue(
+    string descriptor_instance_id,
+    array<uint8> value) => (WebBluetoothResult result);
 };
 
 // Classes should implement this interface and pass an associated pointer
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 05ece5a..15be521 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -5069,6 +5069,19 @@
   </summary>
 </histogram>
 
+<histogram name="Bluetooth.Web.Descriptor.WriteValue.Outcome"
+    enum="WebBluetoothGATTOperationOutcome">
+  <owner>jyasskin@chromium.org</owner>
+  <owner>ortuno@chromium.org</owner>
+  <owner>scheib@chromium.org</owner>
+  <summary>
+    Records the outcome of a call to descriptor.writeValue(). Used to know what
+    types of errors users are seeing. The results will be used to determine how
+    common these errors are and if we need to provide better error messages to
+    the users.
+  </summary>
+</histogram>
+
 <histogram name="Bluetooth.Web.FunctionCall.Count" enum="WebBluetoothFunction">
   <owner>jyasskin@chromium.org</owner>
   <owner>ortuno@chromium.org</owner>
@@ -10097,6 +10110,24 @@
   </summary>
 </histogram>
 
+<histogram name="DesktopIOSPromotion.OAuthTokenCompletion"
+    enum="BooleanSuccess">
+  <owner>justincohen@chromium.org</owner>
+  <summary>
+    Whether getting the OAuth token was successful for a desktop to ios
+    promotion query.
+  </summary>
+</histogram>
+
+<histogram name="DesktopIOSPromotion.OAuthTokenResponseCode"
+    enum="CombinedHttpResponseAndNetErrorCode">
+  <owner>justincohen@chromium.org</owner>
+  <summary>
+    HTTP Response code returned by the server when trying to fetch the OAuth
+    token for a desktop ios promotion query.
+  </summary>
+</histogram>
+
 <histogram name="DesktopIOSPromotion.SMSToSigninTime" units="hours">
   <owner>mrefaat@chromium.org</owner>
   <summary>
@@ -17762,6 +17793,14 @@
   </summary>
 </histogram>
 
+<histogram name="Extensions.LoadOffStoreItems" units="Number of items">
+  <owner>rdevlin.cronin@chromium.org</owner>
+  <summary>
+    The number of enabled extensions or apps the user has installed that do not
+    update from the Chrome Web Store. Recorded during profile initialization.
+  </summary>
+</histogram>
+
 <histogram name="Extensions.LoadPackagedApp">
   <owner>rdevlin.cronin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/presubmit_bad_message_reasons.py b/tools/metrics/histograms/presubmit_bad_message_reasons.py
new file mode 100644
index 0000000..0548607
--- /dev/null
+++ b/tools/metrics/histograms/presubmit_bad_message_reasons.py
@@ -0,0 +1,38 @@
+# 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.
+
+"""Check to see if the various BadMessage enums in histograms.xml need to be
+updated. This can be called from a chromium PRESUBMIT.py to ensure updates to
+bad_message.h also include the generated changes to histograms.xml
+"""
+
+import update_histogram_enum
+
+def PrecheckBadMessage(input_api, output_api, histogram_name):
+  source_path = ''
+
+  # This function is called once per bad_message.h-containing directory. Check
+  # for the |bad_message.h| file, and if present, remember its path.
+  for f in input_api.AffectedFiles():
+    if f.LocalPath().endswith('bad_message.h'):
+      source_path = f.LocalPath()
+      break
+
+  # If the |bad_message.h| wasn't found in this change, then there is nothing to
+  # do and histogram.xml does not need to be updated.
+  if source_path == '':
+    return []
+
+  START_MARKER='^enum (class )?BadMessageReason {'
+  END_MARKER='^BAD_MESSAGE_MAX'
+  if update_histogram_enum.HistogramNeedsUpdate(
+      histogram_enum_name=histogram_name,
+      source_enum_path=source_path,
+      start_marker=START_MARKER,
+      end_marker=END_MARKER):
+    return [output_api.PresubmitPromptWarning(
+        'bad_messages.h has been updated but histogram.xml does not '
+        'appear to be updated.\nPlease run:\n'
+        '  python tools/metrics/histograms/update_bad_message_reasons.py\n')]
+  return []
diff --git a/tools/perf/page_sets/tough_ad_cases.py b/tools/perf/page_sets/tough_ad_cases.py
index d9ea110c..68321d1 100644
--- a/tools/perf/page_sets/tough_ad_cases.py
+++ b/tools/perf/page_sets/tough_ad_cases.py
@@ -189,9 +189,8 @@
         self, scroll=scroll))
     self.AddStory(AdPage('http://androidcentral.com', self, scroll=scroll,
         wait_for_interactive_or_better=True))
-    # Disabled: crbug.com/682349
-    #self.AddStory(AdPage('http://mashable.com', self, scroll=scroll,
-    #    y_scroll_distance_multiplier=0.25))
+    self.AddStory(AdPage('http://mashable.com', self, scroll=scroll,
+        y_scroll_distance_multiplier=0.25))
     self.AddStory(AdPage('http://www.androidauthority.com/'
         'reduce-data-use-turn-on-data-compression-in-chrome-630064/', self,
         scroll=scroll))