diff --git a/DEPS b/DEPS
index dae3980a..62fb64d 100644
--- a/DEPS
+++ b/DEPS
@@ -39,11 +39,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': 'ac66ae63eade5347864c5d0bd025756c5ea23fda',
+  'skia_revision': 'e719577fe8ac3de38795cde2007337f854d97435',
   # 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': 'fd97bfe7190e2b515deed9c138c1617e675baa71',
+  'v8_revision': '6596238f96a0254f040d5c8aefcdef0791ba2ba5',
   # 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.
@@ -63,7 +63,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '878dd5b121b38e6eaba5b3f3a90d0a9abf60feaf',
+  'pdfium_revision': '7c29e27dae139a205755c1a29b7f3ac8b36ec0da',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -99,7 +99,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
-  'libfuzzer_revision': '3e02228ebfee620dd4a2db0ee15a8084ca349183',
+  'libfuzzer_revision': '9aa0bddeb6820f6e5d897da410e1e8a3f7fd4b8e',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
diff --git a/WATCHLISTS b/WATCHLISTS
index b87170c..bb2df83 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -102,6 +102,9 @@
     'arc': {
       'filepath': 'arc/',
     },
+    'arc_auth': {
+      'filepath': 'chrome/browser/chromeos/arc/arc_auth'
+    },
     'arc_net': {
       'filepath': 'components/arc/net/',
     },
@@ -1440,6 +1443,7 @@
             'hidehiko+watch@chromium.org',
             'lhchavez+watch@chromium.org',
             'yusukes+watch@chromium.org'],
+    'arc_auth': ['khmel+watch@chromium.org'],
     'arc_net': ['abhishekbh@chromium.org',
                 'cernekee@chromium.org',
                 'snanda@chromium.org'],
@@ -1570,7 +1574,7 @@
     'blink_indexed_db': ['cmumford@chromium.org',
                          'jsbell+idb@chromium.org'],
     'blink_input': ['dtapuska+blinkwatch@chromium.org',
-                    'nzolghadr+blinkwatch@chromium.org'],
+                    'nzolghadr@chromium.org'],
     'blink_layers': ['blink-layers+watch@chromium.org'],
     'blink_layout': ['blink-reviews-layout@chromium.org',
                      'eae+blinkwatch@chromium.org',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index a5b6062..0acd477 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -320,7 +320,6 @@
     "common/system/keyboard_brightness_control_delegate.h",
     "common/system/locale/locale_notification_controller.cc",
     "common/system/locale/locale_notification_controller.h",
-    "common/system/locale/locale_observer.h",
     "common/system/networking_config_delegate.cc",
     "common/system/networking_config_delegate.h",
     "common/system/overview/overview_button_tray.cc",
diff --git a/ash/aura/wm_root_window_controller_aura.cc b/ash/aura/wm_root_window_controller_aura.cc
index b6dafca..eae1b44 100644
--- a/ash/aura/wm_root_window_controller_aura.cc
+++ b/ash/aura/wm_root_window_controller_aura.cc
@@ -114,12 +114,6 @@
   WmRootWindowController::OnWallpaperAnimationFinished(widget);
 }
 
-void WmRootWindowControllerAura::UpdateAfterLoginStatusChange(
-    LoginStatus status) {
-  root_window_controller_->UpdateAfterLoginStatusChange(status);
-  WmRootWindowController::UpdateAfterLoginStatusChange(status);
-}
-
 bool WmRootWindowControllerAura::ShouldDestroyWindowInCloseChildWindows(
     WmWindow* window) {
   return WmWindowAura::GetAuraWindow(window)->owned_by_parent();
diff --git a/ash/aura/wm_root_window_controller_aura.h b/ash/aura/wm_root_window_controller_aura.h
index 976821b..0b888397 100644
--- a/ash/aura/wm_root_window_controller_aura.h
+++ b/ash/aura/wm_root_window_controller_aura.h
@@ -42,7 +42,6 @@
   gfx::Point GetLastMouseLocationInRoot() override;
   void OnInitialWallpaperAnimationStarted() override;
   void OnWallpaperAnimationFinished(views::Widget* widget) override;
-  void UpdateAfterLoginStatusChange(LoginStatus status) override;
 
  protected:
   // WmRootWindowController:
diff --git a/ash/common/mojo_interface_factory.cc b/ash/common/mojo_interface_factory.cc
index e4384fe..b745571 100644
--- a/ash/common/mojo_interface_factory.cc
+++ b/ash/common/mojo_interface_factory.cc
@@ -4,7 +4,10 @@
 
 #include "ash/common/mojo_interface_factory.h"
 
+#include <utility>
+
 #include "ash/common/shelf/shelf_controller.h"
+#include "ash/common/system/locale/locale_notification_controller.h"
 #include "ash/common/system/tray/system_tray_controller.h"
 #include "ash/common/wm_shell.h"
 #include "base/bind.h"
@@ -14,6 +17,12 @@
 
 namespace {
 
+void BindLocaleNotificationControllerOnMainThread(
+    mojom::LocaleNotificationControllerRequest request) {
+  WmShell::Get()->locale_notification_controller()->BindRequest(
+      std::move(request));
+}
+
 void BindShelfControllerRequestOnMainThread(
     mojom::ShelfControllerRequest request) {
   WmShell::Get()->shelf_controller()->BindRequest(std::move(request));
@@ -30,6 +39,9 @@
 void RegisterInterfaces(
     service_manager::InterfaceRegistry* registry,
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner) {
+  registry->AddInterface(
+      base::Bind(&BindLocaleNotificationControllerOnMainThread),
+      main_thread_task_runner);
   registry->AddInterface(base::Bind(&BindShelfControllerRequestOnMainThread),
                          main_thread_task_runner);
   registry->AddInterface(base::Bind(&BindSystemTrayRequestOnMainThread),
diff --git a/ash/common/system/locale/locale_notification_controller.cc b/ash/common/system/locale/locale_notification_controller.cc
index 4a5e4a1..39444a7 100644
--- a/ash/common/system/locale/locale_notification_controller.cc
+++ b/ash/common/system/locale/locale_notification_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/common/system/locale/locale_notification_controller.h"
 
+#include <memory>
 #include <utility>
 
 #include "ash/common/system/system_notifier.h"
@@ -28,7 +29,9 @@
 
 class LocaleNotificationDelegate : public message_center::NotificationDelegate {
  public:
-  explicit LocaleNotificationDelegate(LocaleObserver::Delegate* delegate);
+  explicit LocaleNotificationDelegate(
+      const base::Callback<void(ash::mojom::LocaleNotificationResult)>&
+          callback);
 
  protected:
   ~LocaleNotificationDelegate() override;
@@ -40,21 +43,28 @@
   void ButtonClick(int button_index) override;
 
  private:
-  LocaleObserver::Delegate* delegate_;
+  base::Callback<void(ash::mojom::LocaleNotificationResult)> callback_;
 
   DISALLOW_COPY_AND_ASSIGN(LocaleNotificationDelegate);
 };
 
 LocaleNotificationDelegate::LocaleNotificationDelegate(
-    LocaleObserver::Delegate* delegate)
-    : delegate_(delegate) {
-  DCHECK(delegate_);
+    const base::Callback<void(ash::mojom::LocaleNotificationResult)>& callback)
+    : callback_(callback) {}
+
+LocaleNotificationDelegate::~LocaleNotificationDelegate() {
+  if (callback_) {
+    // We're being destroyed but the user didn't click on anything. Run the
+    // callback so that we don't crash.
+    callback_.Run(ash::mojom::LocaleNotificationResult::ACCEPT);
+  }
 }
 
-LocaleNotificationDelegate::~LocaleNotificationDelegate() {}
-
 void LocaleNotificationDelegate::Close(bool by_user) {
-  delegate_->AcceptLocaleChange();
+  if (callback_) {
+    callback_.Run(ash::mojom::LocaleNotificationResult::ACCEPT);
+    callback_.Reset();
+  }
 }
 
 bool LocaleNotificationDelegate::HasClickedListener() {
@@ -62,32 +72,37 @@
 }
 
 void LocaleNotificationDelegate::Click() {
-  delegate_->AcceptLocaleChange();
+  if (callback_) {
+    callback_.Run(ash::mojom::LocaleNotificationResult::ACCEPT);
+    callback_.Reset();
+  }
 }
 
 void LocaleNotificationDelegate::ButtonClick(int button_index) {
   DCHECK_EQ(0, button_index);
-  delegate_->RevertLocaleChange();
+
+  if (callback_) {
+    callback_.Run(ash::mojom::LocaleNotificationResult::REVERT);
+    callback_.Reset();
+  }
 }
 
 }  // namespace
 
-LocaleNotificationController::LocaleNotificationController() {
-  WmShell::Get()->system_tray_notifier()->AddLocaleObserver(this);
-}
+LocaleNotificationController::LocaleNotificationController() {}
 
-LocaleNotificationController::~LocaleNotificationController() {
-  WmShell::Get()->system_tray_notifier()->RemoveLocaleObserver(this);
+LocaleNotificationController::~LocaleNotificationController() {}
+
+void LocaleNotificationController::BindRequest(
+    mojom::LocaleNotificationControllerRequest request) {
+  bindings_.AddBinding(this, std::move(request));
 }
 
 void LocaleNotificationController::OnLocaleChanged(
-    LocaleObserver::Delegate* delegate,
     const std::string& cur_locale,
     const std::string& from_locale,
-    const std::string& to_locale) {
-  if (!delegate)
-    return;
-
+    const std::string& to_locale,
+    const OnLocaleChangedCallback& callback) {
   base::string16 from =
       l10n_util::GetDisplayNameForLocale(from_locale, cur_locale, true);
   base::string16 to =
@@ -109,7 +124,7 @@
       base::string16() /* display_source */, GURL(),
       message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
                                  system_notifier::kNotifierLocale),
-      optional, new LocaleNotificationDelegate(delegate)));
+      optional, new LocaleNotificationDelegate(callback)));
   message_center::MessageCenter::Get()->AddNotification(
       std::move(notification));
 }
diff --git a/ash/common/system/locale/locale_notification_controller.h b/ash/common/system/locale/locale_notification_controller.h
index 24f27a6f..b97c7ed 100644
--- a/ash/common/system/locale/locale_notification_controller.h
+++ b/ash/common/system/locale/locale_notification_controller.h
@@ -7,28 +7,37 @@
 
 #include <string>
 
-#include "ash/common/system/locale/locale_observer.h"
+#include "ash/public/interfaces/locale.mojom.h"
 #include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
 
 namespace ash {
 
 // Observes the locale change and creates rich notification for the change.
-class LocaleNotificationController : public LocaleObserver {
+class LocaleNotificationController
+    : public mojom::LocaleNotificationController {
  public:
   LocaleNotificationController();
   ~LocaleNotificationController() override;
 
+  // Binds the mojom::LocaleNotificationController interface request to this
+  // object.
+  void BindRequest(mojom::LocaleNotificationControllerRequest request);
+
  private:
-  // Overridden from LocaleObserver.
-  void OnLocaleChanged(LocaleObserver::Delegate* delegate,
-                       const std::string& cur_locale,
+  // Overridden from mojom::LocaleNotificationController:
+  void OnLocaleChanged(const std::string& cur_locale,
                        const std::string& from_locale,
-                       const std::string& to_locale) override;
+                       const std::string& to_locale,
+                       const OnLocaleChangedCallback& callback) override;
 
   std::string cur_locale_;
   std::string from_locale_;
   std::string to_locale_;
 
+  // Bindings for the LocaleNotificationController interface.
+  mojo::BindingSet<mojom::LocaleNotificationController> bindings_;
+
   DISALLOW_COPY_AND_ASSIGN(LocaleNotificationController);
 };
 
diff --git a/ash/common/system/locale/locale_observer.h b/ash/common/system/locale/locale_observer.h
deleted file mode 100644
index 96b76d4..0000000
--- a/ash/common/system/locale/locale_observer.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_COMMON_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
-#define ASH_COMMON_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
-
-#include <string>
-
-#include "ash/ash_export.h"
-
-namespace ash {
-
-class ASH_EXPORT LocaleObserver {
- public:
-  class Delegate {
-   public:
-    virtual ~Delegate() {}
-
-    virtual void AcceptLocaleChange() = 0;
-    virtual void RevertLocaleChange() = 0;
-  };
-
-  virtual ~LocaleObserver() {}
-
-  virtual void OnLocaleChanged(Delegate* delegate,
-                               const std::string& cur_locale,
-                               const std::string& from_locale,
-                               const std::string& to_locale) = 0;
-};
-
-}  // namespace ash
-
-#endif  // ASH_COMMON_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
diff --git a/ash/common/system/tray/system_tray_notifier.cc b/ash/common/system/tray/system_tray_notifier.cc
index b424aa0..62b79b48b 100644
--- a/ash/common/system/tray/system_tray_notifier.cc
+++ b/ash/common/system/tray/system_tray_notifier.cc
@@ -129,22 +129,6 @@
     observer.OnIMEMenuActivationChanged(is_active);
 }
 
-void SystemTrayNotifier::AddLocaleObserver(LocaleObserver* observer) {
-  locale_observers_.AddObserver(observer);
-}
-
-void SystemTrayNotifier::RemoveLocaleObserver(LocaleObserver* observer) {
-  locale_observers_.RemoveObserver(observer);
-}
-
-void SystemTrayNotifier::NotifyLocaleChanged(LocaleObserver::Delegate* delegate,
-                                             const std::string& cur_locale,
-                                             const std::string& from_locale,
-                                             const std::string& to_locale) {
-  for (auto& observer : locale_observers_)
-    observer.OnLocaleChanged(delegate, cur_locale, from_locale, to_locale);
-}
-
 void SystemTrayNotifier::AddUpdateObserver(UpdateObserver* observer) {
   update_observers_.AddObserver(observer);
 }
diff --git a/ash/common/system/tray/system_tray_notifier.h b/ash/common/system/tray/system_tray_notifier.h
index 29bb9f5..3051575 100644
--- a/ash/common/system/tray/system_tray_notifier.h
+++ b/ash/common/system/tray/system_tray_notifier.h
@@ -9,7 +9,6 @@
 
 #include "ash/ash_export.h"
 #include "ash/common/accessibility_types.h"
-#include "ash/common/system/locale/locale_observer.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 
@@ -78,14 +77,6 @@
   void NotifyRefreshIME();
   void NotifyRefreshIMEMenu(bool is_active);
 
-  // Locale.
-  void AddLocaleObserver(LocaleObserver* observer);
-  void RemoveLocaleObserver(LocaleObserver* observer);
-  void NotifyLocaleChanged(LocaleObserver::Delegate* delegate,
-                           const std::string& cur_locale,
-                           const std::string& from_locale,
-                           const std::string& to_locale);
-
   // OS updates.
   void AddUpdateObserver(UpdateObserver* observer);
   void RemoveUpdateObserver(UpdateObserver* observer);
@@ -173,7 +164,6 @@
   base::ObserverList<AudioObserver> audio_observers_;
   base::ObserverList<ClockObserver> clock_observers_;
   base::ObserverList<IMEObserver> ime_observers_;
-  base::ObserverList<LocaleObserver> locale_observers_;
   base::ObserverList<UpdateObserver> update_observers_;
   base::ObserverList<UserObserver> user_observers_;
 
diff --git a/ash/common/wm/root_window_layout_manager.cc b/ash/common/wm/root_window_layout_manager.cc
index b3e9f52..dda5384 100644
--- a/ash/common/wm/root_window_layout_manager.cc
+++ b/ash/common/wm/root_window_layout_manager.cc
@@ -29,10 +29,18 @@
   WmWindowTracker children_tracker(owner_->GetChildren());
   while (!children_tracker.windows().empty()) {
     WmWindow* child = children_tracker.Pop();
+    // Skip descendants of top-level windows, i.e. only resize containers and
+    // other windows without a delegate, such as ScreenDimmer windows.
+    if (child->GetToplevelWindow())
+      continue;
+
     child->SetBounds(fullscreen_bounds);
     WmWindowTracker grandchildren_tracker(child->GetChildren());
-    while (!grandchildren_tracker.windows().empty())
-      grandchildren_tracker.Pop()->SetBounds(fullscreen_bounds);
+    while (!grandchildren_tracker.windows().empty()) {
+      child = grandchildren_tracker.Pop();
+      if (!child->GetToplevelWindow())
+        child->SetBounds(fullscreen_bounds);
+    }
   }
 }
 
diff --git a/ash/common/wm_shell.cc b/ash/common/wm_shell.cc
index b78a7939..7bee335a 100644
--- a/ash/common/wm_shell.cc
+++ b/ash/common/wm_shell.cc
@@ -23,6 +23,7 @@
 #include "ash/common/shell_window_ids.h"
 #include "ash/common/system/brightness_control_delegate.h"
 #include "ash/common/system/keyboard_brightness_control_delegate.h"
+#include "ash/common/system/locale/locale_notification_controller.h"
 #include "ash/common/system/toast/toast_manager.h"
 #include "ash/common/system/tray/system_tray_controller.h"
 #include "ash/common/system/tray/system_tray_delegate.h"
@@ -233,6 +234,8 @@
     : delegate_(std::move(shell_delegate)),
       focus_cycler_(base::MakeUnique<FocusCycler>()),
       immersive_context_(base::MakeUnique<ImmersiveContextAsh>()),
+      locale_notification_controller_(
+          base::MakeUnique<LocaleNotificationController>()),
       shelf_controller_(base::MakeUnique<ShelfController>()),
       system_tray_controller_(base::MakeUnique<SystemTrayController>(
           delegate_->GetShellConnector())),
diff --git a/ash/common/wm_shell.h b/ash/common/wm_shell.h
index f1e45dd..ab46e16 100644
--- a/ash/common/wm_shell.h
+++ b/ash/common/wm_shell.h
@@ -54,6 +54,7 @@
 class KeyEventWatcher;
 class KeyboardBrightnessControlDelegate;
 class KeyboardUI;
+class LocaleNotificationController;
 class MaximizeModeController;
 class MruWindowTracker;
 class NewWindowDelegate;
@@ -128,6 +129,10 @@
 
   KeyboardUI* keyboard_ui() { return keyboard_ui_.get(); }
 
+  LocaleNotificationController* locale_notification_controller() {
+    return locale_notification_controller_.get();
+  }
+
   MaximizeModeController* maximize_mode_controller() {
     return maximize_mode_controller_.get();
   }
@@ -472,6 +477,7 @@
   std::unique_ptr<KeyboardBrightnessControlDelegate>
       keyboard_brightness_control_delegate_;
   std::unique_ptr<KeyboardUI> keyboard_ui_;
+  std::unique_ptr<LocaleNotificationController> locale_notification_controller_;
   std::unique_ptr<MaximizeModeController> maximize_mode_controller_;
   std::unique_ptr<MediaDelegate> media_delegate_;
   std::unique_ptr<MruWindowTracker> mru_window_tracker_;
diff --git a/ash/mus/manifest.json b/ash/mus/manifest.json
index 76ffade..1fab12a1 100644
--- a/ash/mus/manifest.json
+++ b/ash/mus/manifest.json
@@ -5,6 +5,7 @@
   "capabilities": {
     "provided": {
       "ash": [
+        "ash::mojom::LocaleNotificationController",
         "ash::mojom::ShelfController",
         "ash::mojom::SystemTray",
         "ash::mojom::WallpaperController"
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 0570864b..c2e08a11 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "ash_window_type.mojom",
     "container.mojom",
+    "locale.mojom",
     "shelf.mojom",
     "system_tray.mojom",
     "wallpaper.mojom",
diff --git a/ash/public/interfaces/locale.mojom b/ash/public/interfaces/locale.mojom
new file mode 100644
index 0000000..756430d8
--- /dev/null
+++ b/ash/public/interfaces/locale.mojom
@@ -0,0 +1,21 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module ash.mojom;
+
+// Sent as the response to LocaleNotificationController.OnLocaleChanged().
+enum LocaleNotificationResult {
+  ACCEPT,
+  REVERT
+};
+
+// Used by Chrome to make ash show a notification.
+interface LocaleNotificationController {
+  // Displays a notification in ash prompting the user whether to accept a
+  // change in the locale. If the user clicks the accept button (or closes the
+  // notification), OnLocaleChange() returns ACCEPT. If the user clicks the
+  // revert button, returns REVERT.
+  OnLocaleChanged(string current, string from, string to)
+      => (LocaleNotificationResult result);
+};
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index 286f072..12cfeda 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -63,11 +63,9 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/window.h"
-#include "ui/aura/window_delegate.h"
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/aura/window_observer.h"
 #include "ui/aura/window_tracker.h"
-#include "ui/base/hit_test.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/keyboard/keyboard_controller.h"
@@ -145,43 +143,6 @@
   return true;
 }
 
-// A window delegate which does nothing. Used to create a window that
-// is a event target, but do nothing.
-class EmptyWindowDelegate : public aura::WindowDelegate {
- public:
-  EmptyWindowDelegate() {}
-  ~EmptyWindowDelegate() override {}
-
-  // aura::WindowDelegate overrides:
-  gfx::Size GetMinimumSize() const override { return gfx::Size(); }
-  gfx::Size GetMaximumSize() const override { return gfx::Size(); }
-  void OnBoundsChanged(const gfx::Rect& old_bounds,
-                       const gfx::Rect& new_bounds) override {}
-  gfx::NativeCursor GetCursor(const gfx::Point& point) override {
-    return gfx::kNullCursor;
-  }
-  int GetNonClientComponent(const gfx::Point& point) const override {
-    return HTNOWHERE;
-  }
-  bool ShouldDescendIntoChildForEventHandling(
-      aura::Window* child,
-      const gfx::Point& location) override {
-    return false;
-  }
-  bool CanFocus() override { return false; }
-  void OnCaptureLost() override {}
-  void OnPaint(const ui::PaintContext& context) override {}
-  void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
-  void OnWindowDestroying(aura::Window* window) override {}
-  void OnWindowDestroyed(aura::Window* window) override { delete this; }
-  void OnWindowTargetVisibilityChanged(bool visible) override {}
-  bool HasHitTestMask() const override { return false; }
-  void GetHitTestMask(gfx::Path* mask) const override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(EmptyWindowDelegate);
-};
-
 }  // namespace
 
 void RootWindowController::CreateForPrimaryDisplay(AshWindowTreeHost* host) {
@@ -311,11 +272,6 @@
   return ash_host_->AsWindowTreeHost()->window()->GetChildById(container_id);
 }
 
-void RootWindowController::UpdateAfterLoginStatusChange(LoginStatus status) {
-  if (status != LoginStatus::NOT_LOGGED_IN)
-    mouse_event_target_.reset();
-}
-
 void RootWindowController::OnInitialWallpaperAnimationStarted() {
 #if defined(OS_CHROMEOS)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -338,8 +294,6 @@
 }
 
 void RootWindowController::CloseChildWindows() {
-  mouse_event_target_.reset();
-
   // Remove observer as deactivating keyboard causes
   // docked_window_layout_manager() to fire notifications.
   if (docked_window_layout_manager() &&
@@ -530,20 +484,6 @@
   status_container->SetEventTargeter(base::MakeUnique<ShelfWindowTargeter>(
       wm_status_container, wm_shelf_aura_.get()));
 
-  if (!WmShell::Get()
-           ->GetSessionStateDelegate()
-           ->IsActiveUserSessionStarted()) {
-    // This window exists only to be a event target on login screen.
-    // It does not have to handle events, nor be visible.
-    mouse_event_target_.reset(new aura::Window(new EmptyWindowDelegate));
-    mouse_event_target_->Init(ui::LAYER_NOT_DRAWN);
-
-    aura::Window* lock_wallpaper_container =
-        GetContainer(kShellWindowId_LockScreenWallpaperContainer);
-    lock_wallpaper_container->AddChild(mouse_event_target_.get());
-    mouse_event_target_->Show();
-  }
-
   panel_container_handler_ = base::MakeUnique<PanelWindowEventHandler>();
   GetContainer(kShellWindowId_PanelContainer)
       ->AddPreTargetHandler(panel_container_handler_.get());
diff --git a/ash/root_window_controller.h b/ash/root_window_controller.h
index 2cd9a7c6..f70d2dd 100644
--- a/ash/root_window_controller.h
+++ b/ash/root_window_controller.h
@@ -143,10 +143,6 @@
   aura::Window* GetContainer(int container_id);
   const aura::Window* GetContainer(int container_id) const;
 
-  // Called when the login status changes after login (such as lock/unlock).
-  // TODO(oshima): Investigate if we can merge this and |OnLoginStateChanged|.
-  void UpdateAfterLoginStatusChange(LoginStatus status);
-
   // Called when the brightness/grayscale animation from white to the login
   // wallpaper image has started.  Starts |boot_splash_screen_|'s hiding
   // animation (if the screen is non-NULL).
@@ -233,13 +229,6 @@
   // to it during construction of the shelf widget and status tray.
   std::unique_ptr<WmShelfAura> wm_shelf_aura_;
 
-  // An invisible/empty window used as a event target for
-  // |MouseCursorEventFilter| before a user logs in.
-  // (crbug.com/266987)
-  // Its container is |LockScreenWallpaperContainer| and
-  // this must be deleted before the container is deleted.
-  std::unique_ptr<aura::Window> mouse_event_target_;
-
   std::unique_ptr<SystemWallpaperController> system_wallpaper_;
 
 #if defined(OS_CHROMEOS)
diff --git a/ash/root_window_controller_unittest.cc b/ash/root_window_controller_unittest.cc
index 969a5f1..192ec46 100644
--- a/ash/root_window_controller_unittest.cc
+++ b/ash/root_window_controller_unittest.cc
@@ -695,20 +695,6 @@
   delete window2;
 }
 
-typedef test::NoSessionAshTestBase NoSessionRootWindowControllerTest;
-
-// Make sure that an event handler exists for entire display area.
-TEST_F(NoSessionRootWindowControllerTest, Event) {
-  aura::Window* root = Shell::GetPrimaryRootWindow();
-  const gfx::Size size = root->bounds().size();
-  EXPECT_TRUE(root->GetEventHandlerForPoint(gfx::Point(0, 0)));
-  EXPECT_TRUE(root->GetEventHandlerForPoint(gfx::Point(0, size.height() - 1)));
-  EXPECT_TRUE(root->GetEventHandlerForPoint(gfx::Point(size.width() - 1, 0)));
-  EXPECT_TRUE(root->GetEventHandlerForPoint(gfx::Point(0, size.height() - 1)));
-  EXPECT_TRUE(root->GetEventHandlerForPoint(
-      gfx::Point(size.width() - 1, size.height() - 1)));
-}
-
 class VirtualKeyboardRootWindowControllerTest
     : public RootWindowControllerTest {
  public:
diff --git a/ash/shell.cc b/ash/shell.cc
index a5d27a9..896dfd7b 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -26,7 +26,6 @@
 #include "ash/common/shelf/wm_shelf.h"
 #include "ash/common/shell_delegate.h"
 #include "ash/common/shell_window_ids.h"
-#include "ash/common/system/locale/locale_notification_controller.h"
 #include "ash/common/system/status_area_widget.h"
 #include "ash/common/system/tray/system_tray_delegate.h"
 #include "ash/common/wallpaper/wallpaper_delegate.h"
@@ -475,8 +474,6 @@
     root->GetRootWindowController()->GetShelf()->ShutdownShelfWidget();
   wm_shell_->DeleteSystemTrayDelegate();
 
-  locale_notification_controller_.reset();
-
   // Drag-and-drop must be canceled prior to close all windows.
   drag_drop_controller_.reset();
 
@@ -778,8 +775,6 @@
   wm_shell_->SetSystemTrayDelegate(
       base::WrapUnique(wm_shell_->delegate()->CreateSystemTrayDelegate()));
 
-  locale_notification_controller_.reset(new LocaleNotificationController);
-
 #if defined(OS_CHROMEOS)
   // Create TouchTransformerController before
   // WindowTreeHostManager::InitDisplays()
diff --git a/ash/shell.h b/ash/shell.h
index 7989a78..a5f5e6e 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -87,7 +87,6 @@
 class ImmersiveHandlerFactoryAsh;
 class LaserPointerController;
 class LinkHandlerModelFactory;
-class LocaleNotificationController;
 class LockStateController;
 enum class LoginStatus;
 class MagnificationController;
@@ -494,7 +493,6 @@
   std::unique_ptr<DisplayConfigurationController>
       display_configuration_controller_;
 
-  std::unique_ptr<LocaleNotificationController> locale_notification_controller_;
   std::unique_ptr<ScreenPinningController> screen_pinning_controller_;
 
 #if defined(OS_CHROMEOS)
diff --git a/base/task_scheduler/scheduler_worker.cc b/base/task_scheduler/scheduler_worker.cc
index 59b756c..e64ca61 100644
--- a/base/task_scheduler/scheduler_worker.cc
+++ b/base/task_scheduler/scheduler_worker.cc
@@ -217,8 +217,6 @@
 }
 
 void SchedulerWorker::JoinForTesting() {
-  // It's not possible to have concurrent calls to this method because a DCHECK
-  // fails when AtomicFlag::Set() calls aren't sequenced.
   DCHECK(!should_exit_for_testing_.IsSet());
   should_exit_for_testing_.Set();
 
diff --git a/blimp/BUILD.gn b/blimp/BUILD.gn
index cdc5752..03ca1ae 100644
--- a/blimp/BUILD.gn
+++ b/blimp/BUILD.gn
@@ -52,6 +52,7 @@
   deps = [
     "//blimp/client:unit_tests",
     "//blimp/common:unit_tests",
+    "//blimp/helium:unit_tests",
     "//blimp/net:unit_tests",
     "//blimp/test:run_all_unittests",
   ]
diff --git a/blimp/helium/result.h b/blimp/helium/result.h
index 5d17e01a..bbc2f1e 100644
--- a/blimp/helium/result.h
+++ b/blimp/helium/result.h
@@ -5,6 +5,8 @@
 #ifndef BLIMP_HELIUM_RESULT_H_
 #define BLIMP_HELIUM_RESULT_H_
 
+#include "blimp/helium/blimp_helium_export.h"
+
 namespace blimp {
 namespace helium {
 
@@ -22,7 +24,7 @@
 };
 
 // Gets a human-readable string representation of |result|.
-const char* ResultToString(Result result);
+BLIMP_HELIUM_EXPORT const char* ResultToString(Result result);
 
 }  // namespace helium
 }  // namespace blimp
diff --git a/blimp/helium/result_unittest.cc b/blimp/helium/result_unittest.cc
index bc68740..3f11fc76 100644
--- a/blimp/helium/result_unittest.cc
+++ b/blimp/helium/result_unittest.cc
@@ -17,7 +17,7 @@
   ~ResultTest() override {}
 };
 
-TEST_F(ResultTest, ResultToString) {
+TEST_F(ResultTest, ResultToStringWorks) {
   // The exhaustive list of errors need not be specified here, but enough are
   // specified that we can verify that the switch/case mapping works as
   // intended.
diff --git a/blimp/helium/stream.h b/blimp/helium/stream.h
index 119e433..2df1aeb 100644
--- a/blimp/helium/stream.h
+++ b/blimp/helium/stream.h
@@ -48,4 +48,4 @@
 }  // namespace helium
 }  // namespace blimp
 
-#endif  // BLIMP_NET_HELIUM_HELIUM_STREAM_H_
+#endif  // BLIMP_HELIUM_STREAM_H_
diff --git a/build/args/bots/chromium.fyi/blimp_client_rel.gn b/build/args/bots/chromium.fyi/blimp_client_rel.gn
index a25f072..b59d55e 100644
--- a/build/args/bots/chromium.fyi/blimp_client_rel.gn
+++ b/build/args/bots/chromium.fyi/blimp_client_rel.gn
@@ -1,3 +1,4 @@
 import("//build/args/blimp_client.gn")
 is_debug = false
 dcheck_always_on = true
+use_goma = true
diff --git a/build/args/bots/chromium.fyi/blimp_engine_rel.gn b/build/args/bots/chromium.fyi/blimp_engine_rel.gn
index 7a8261a..05dc14b61 100644
--- a/build/args/bots/chromium.fyi/blimp_engine_rel.gn
+++ b/build/args/bots/chromium.fyi/blimp_engine_rel.gn
@@ -9,3 +9,4 @@
 is_debug = false
 is_official_build = true
 dcheck_always_on = true
+use_goma = true
diff --git a/cc/test/layer_tree_host_remote_for_testing.cc b/cc/test/layer_tree_host_remote_for_testing.cc
index edba23d..9c48b89 100644
--- a/cc/test/layer_tree_host_remote_for_testing.cc
+++ b/cc/test/layer_tree_host_remote_for_testing.cc
@@ -68,9 +68,7 @@
   }
   void BeginMainFrameNotExpectedSoon() override {}
   void DidBeginMainFrame() override {}
-  void UpdateLayerTreeHost() override {
-    layer_tree_host_remote_->UpdateStateOnInProcessHost();
-  }
+  void UpdateLayerTreeHost() override {}
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,
                            const gfx::Vector2dF& elastic_overscroll_delta,
@@ -290,26 +288,35 @@
 
 void LayerTreeHostRemoteForTesting::ProcessRemoteCompositorUpdate(
     std::unique_ptr<CompositorProtoState> compositor_proto_state) {
-  pending_compositor_proto_state_ = std::move(compositor_proto_state);
-}
+  DCHECK(layer_tree_host_in_process_->CommitRequested());
+  // Deserialize the update from the remote host into client side LTH in
+  // process. This bypasses the network layer.
+  const proto::LayerTreeHost& layer_tree_host_proto =
+      compositor_proto_state->compositor_message->layer_tree_host();
+  compositor_state_deserializer_->DeserializeCompositorUpdate(
+      layer_tree_host_proto);
 
-void LayerTreeHostRemoteForTesting::UpdateStateOnInProcessHost() {
-  // When the InProcess host asks us to update, we de-serialize the update from
-  // the remote host.
-  if (pending_compositor_proto_state_) {
-    const proto::LayerTreeHost& layer_tree_host_proto =
-        pending_compositor_proto_state_->compositor_message->layer_tree_host();
-    compositor_state_deserializer_->DeserializeCompositorUpdate(
-        layer_tree_host_proto);
+  const proto::LayerUpdate& layer_updates =
+      compositor_proto_state->compositor_message->layer_tree_host()
+          .layer_updates();
+  for (int i = 0; i < layer_updates.layers_size(); ++i) {
+    int engine_layer_id = layer_updates.layers(i).id();
+    Layer* engine_layer = GetLayerTree()->LayerById(engine_layer_id);
+    Layer* client_layer =
+        compositor_state_deserializer_->GetLayerForEngineId(engine_layer_id);
 
-    pending_compositor_proto_state_ = nullptr;
-
-    // The only case where the remote host would give a compositor update is if
-    // they wanted the main frame to go till the commit pipeline stage. So
-    // request one to make sure that the in process main frame also goes till
-    // the commit step.
-    layer_tree_host_in_process_->SetNeedsCommit();
+    // Copy test only layer data that are not serialized into network messages.
+    // So in test cases, layers on the client have the same states as their
+    // corresponding layers on the engine.
+    client_layer->SetForceRenderSurfaceForTesting(
+        engine_layer->force_render_surface_for_testing());
   }
+
+  // The only case where the remote host would give a compositor update is if
+  // they wanted the main frame to go till the commit pipeline stage. So
+  // request one to make sure that the in process main frame also goes till
+  // the commit step.
+  layer_tree_host_in_process_->SetNeedsCommit();
 }
 
 }  // namespace cc
diff --git a/cc/test/layer_tree_host_remote_for_testing.h b/cc/test/layer_tree_host_remote_for_testing.h
index 14389b9..2af6722 100644
--- a/cc/test/layer_tree_host_remote_for_testing.h
+++ b/cc/test/layer_tree_host_remote_for_testing.h
@@ -94,12 +94,10 @@
   void RemoteHostNeedsMainFrame();
   void ProcessRemoteCompositorUpdate(
       std::unique_ptr<CompositorProtoState> compositor_proto_state);
-  void UpdateStateOnInProcessHost();
 
   std::unique_ptr<LayerTreeHostInProcess> layer_tree_host_in_process_;
   std::unique_ptr<CompositorStateDeserializer> compositor_state_deserializer_;
 
-  std::unique_ptr<CompositorProtoState> pending_compositor_proto_state_;
   ScrollOffsetMap layers_scrolled_;
 
   std::unique_ptr<LayerTreeHostInProcessClient>
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 0cf3b4c0..e0eb2f0 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -81,25 +81,25 @@
 
 class LayerTreeHostTestHasImplThreadTest : public LayerTreeHostTest {
  public:
-  LayerTreeHostTestHasImplThreadTest() : threaded_(false) {}
+  LayerTreeHostTestHasImplThreadTest() : single_threaded_(false) {}
 
   void RunTest(CompositorMode mode) override {
-    threaded_ = mode == CompositorMode::THREADED;
+    single_threaded_ = mode == CompositorMode::SINGLE_THREADED;
     LayerTreeHostTest::RunTest(mode);
   }
 
   void BeginTest() override {
-    EXPECT_EQ(threaded_, HasImplThread());
+    EXPECT_EQ(single_threaded_, !HasImplThread());
     EndTest();
   }
 
-  void AfterTest() override { EXPECT_EQ(threaded_, HasImplThread()); }
+  void AfterTest() override { EXPECT_EQ(single_threaded_, !HasImplThread()); }
 
  private:
-  bool threaded_;
+  bool single_threaded_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestHasImplThreadTest);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestHasImplThreadTest);
 
 class LayerTreeHostTestSetNeedsCommitInsideLayout : public LayerTreeHostTest {
  protected:
@@ -118,7 +118,7 @@
   void AfterTest() override {}
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommitInsideLayout);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestSetNeedsCommitInsideLayout);
 
 class LayerTreeHostTestFrameOrdering : public LayerTreeHostTest {
  protected:
@@ -193,7 +193,7 @@
   ImplOrder impl_ = IMPL_START;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestFrameOrdering);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestFrameOrdering);
 
 class LayerTreeHostTestSetNeedsUpdateInsideLayout : public LayerTreeHostTest {
  protected:
@@ -212,7 +212,7 @@
   void AfterTest() override {}
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsUpdateInsideLayout);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestSetNeedsUpdateInsideLayout);
 
 // Test if the LTHI receives ReadyToActivate notifications from the TileManager
 // when no raster tasks get scheduled.
@@ -256,7 +256,7 @@
   size_t required_for_activation_count_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestReadyToActivateEmpty);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestReadyToActivateEmpty);
 
 // Test if the LTHI receives ReadyToActivate notifications from the TileManager
 // when some raster tasks flagged as REQUIRED_FOR_ACTIVATION got scheduled.
@@ -285,9 +285,9 @@
   FakeContentLayerClient client_;
 };
 
-// Multi-thread only because in single thread the commit goes directly to the
-// active tree, so notify ready to activate is skipped.
-MULTI_THREAD_TEST_F(LayerTreeHostTestReadyToActivateNonEmpty);
+// No single thread test because the commit goes directly to the active tree in
+// single thread mode, so notify ready to activate is skipped.
+REMOTE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestReadyToActivateNonEmpty);
 
 // Test if the LTHI receives ReadyToDraw notifications from the TileManager when
 // no raster tasks get scheduled.
@@ -327,7 +327,7 @@
   size_t required_for_draw_count_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestReadyToDrawEmpty);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestReadyToDrawEmpty);
 
 // Test if the LTHI receives ReadyToDraw notifications from the TileManager when
 // some raster tasks flagged as REQUIRED_FOR_DRAW got scheduled.
@@ -434,6 +434,8 @@
 // single threaded mode.
 SINGLE_THREAD_TEST_F(LayerTreeHostTestReadyToDrawVisibility);
 
+// Since the LayerTreeHostContextCacheTests exclusively tests the behavior of
+// LayerTreeHostImpl, they don't need to run for the remote mode.
 class LayerTreeHostContextCacheTest : public LayerTreeHostTest {
  public:
   std::unique_ptr<TestCompositorFrameSink> CreateCompositorFrameSink(
@@ -654,7 +656,7 @@
   int num_draws_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommit1);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestSetNeedsCommit1);
 
 // A SetNeedsCommit should lead to 1 commit. Issuing a second commit after that
 // first committed frame draws should lead to another commit.
@@ -690,7 +692,7 @@
   int num_draws_;
 };
 
-MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommit2);
+REMOTE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommit2);
 
 // Verify that we pass property values in PushPropertiesTo.
 class LayerTreeHostTestPushPropertiesTo : public LayerTreeHostTest {
@@ -785,7 +787,7 @@
   int index_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestPushPropertiesTo);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestPushPropertiesTo);
 
 class LayerTreeHostTestPushNodeOwnerToNodeIdMap : public LayerTreeHostTest {
  protected:
@@ -907,6 +909,11 @@
   scoped_refptr<Layer> child_;
 };
 
+// This test compares the value of tree indexes from Layers on the engine to the
+// resulting PropertyTrees copied to the pending tree after the commit on the
+// client. This will result in a failure for LTH remote test since while the
+// client side Layers would have the correct values for these indexes, but the
+// engine will never build PropertyTrees in LTH remote. See crbug.com/655795.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestPushNodeOwnerToNodeIdMap);
 
 class LayerTreeHostTestSurfaceDamage : public LayerTreeHostTest {
@@ -988,6 +995,12 @@
   scoped_refptr<Layer> grand_child_;
 };
 
+// This test changes properties on the Layer and ensures that the subtree damage
+// is tracked correctly on the resulting RenderSurfaceImpl for the corresponding
+// LayerImpl. Since the remote code path currently synchronizes the hierarchy
+// between the engine and client for every frame, all Layers on the client end
+// up being marked as damaged.
+// Enable this when crbug.com/605170 is fixed.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSurfaceDamage);
 
 // Verify damage status of property trees is preserved after commit.
@@ -1199,6 +1212,9 @@
   FilterOperations sepia_filter_;
 };
 
+// This test verifies that correct values are retained on the impl thread in
+// cases where they are animated on that thread. Since remote mode doesn't
+// support threaded animations, we don't need to run this in remote mode.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestEffectTreeSync);
 
 class LayerTreeHostTestTransformTreeSync : public LayerTreeHostTest {
@@ -1276,6 +1292,9 @@
   scoped_refptr<Layer> root_;
 };
 
+// This test verifies that correct values are retained on the impl thread in
+// cases where they are animated on that thread. Since remote mode doesn't
+// support threaded animations, we don't need to run this in remote mode.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestTransformTreeSync);
 
 // Verify damage status is updated even when the transform tree doesn't need
@@ -1350,6 +1369,9 @@
   scoped_refptr<Layer> grand_child_;
 };
 
+// This test verifies that correct values are retained on the impl thread in
+// cases where they are animated on that thread. Since remote mode doesn't
+// support threaded animations, we don't need to run this in remote mode.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestTransformTreeDamageIsUpdated);
 
 // Test that when mask layers switches layers, this gets pushed onto impl.
@@ -1420,6 +1442,12 @@
   int index_;
 };
 
+// This test also verifies that the Layers updated in a main frame correspond
+// only to the Layers that need updates computed using
+// draw_property_utils::FindLayersThatNeedUpdates. Since LayerTreeHostRemote
+// currently updates all Layers during the main frame, the test fails for remote
+// mode.
+// TODO(xingliu): Revisit enabling this when crbug.com/650885 is resolved.
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSwitchMaskLayer);
 
 // 1 setNeedsRedraw after the first commit has completed should lead to 1
@@ -1456,7 +1484,7 @@
   int num_draws_;
 };
 
-MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsRedraw);
+REMOTE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsRedraw);
 
 // After setNeedsRedrawRect(invalid_rect) the final damage_rect
 // must contain invalid_rect.
@@ -1514,7 +1542,7 @@
   scoped_refptr<FakePictureLayer> root_layer_;
 };
 
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsRedrawRect);
+SINGLE_MULTI_AND_REMOTE_TEST_F(LayerTreeHostTestSetNeedsRedrawRect);
 
 // Ensure the texture size of the pending and active trees are identical when a
 // layer is not in the viewport and a resize happens on the viewport
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
index fe2200e..7531207 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
 import android.widget.Button;
 import android.widget.CheckBox;
@@ -428,6 +429,21 @@
                 }
             });
         }
+
+        // If some of the required fields are valid, then this EditorView is for modification, so
+        // validate form to update displayed errors and hide keyboard by default.
+        if (hasValidRequiredField()) {
+            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    validateForm();
+                }
+            });
+        } else {
+            getWindow().setSoftInputMode(
+                    WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        }
     }
 
     /** Rereads the values in the model to update the UI. */
@@ -456,6 +472,15 @@
         }
     }
 
+    private boolean hasValidRequiredField() {
+        List<EditorFieldModel> fields = mEditorModel.getFields();
+        for (int i = 0; i < fields.size(); i++) {
+            EditorFieldModel field = fields.get(i);
+            if (field.isRequired() && field.isValid()) return true;
+        }
+        return false;
+    }
+
     private List<EditorFieldView> getViewsWithInvalidInformation(boolean findAll) {
         List<EditorFieldView> invalidViews = new ArrayList<>();
         for (int i = 0; i < mFieldViews.size(); i++) {
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index bff3c0d..0c176b9 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -2296,9 +2296,6 @@
   <message name="IDS_OPTIONS_SETTINGS_SECTION_TITLE_BLUETOOTH">
     Bluetooth
   </message>
-  <message name="IDS_OPTIONS_SETTINGS_SECTION_TITLE_LANGUAGE">
-    Language
-  </message>
   <message name="IDS_OPTIONS_DEVICE_GROUP_BATTERY_STATUS_LABEL" desc="The prefix to the battery status in the device section.">
     Battery - <ph name="status">$1<ex>56% - 2:03 left</ex></ph>
   </message>
@@ -5696,14 +5693,6 @@
     Visit captive portal login page
   </message>
 
-  <!-- Locale Change Notification-->
-  <message name="IDS_LOCALE_CHANGE_MESSAGE" desc="Message shown when locale was changed based on profile content.">
-    Chrome's language has changed from "<ph name="FROM_LOCALE">$1<ex>Italian</ex></ph>" to "<ph name="TO_LOCALE">$2<ex>English (United States)</ex></ph>" after syncing your settings.
-  </message>
-  <message name="IDS_LOCALE_CHANGE_REVERT_MESSAGE" desc="Link to revert a change.">
-    Change back to "<ph name="FROM_LOCALE">$1<ex>Italian</ex></ph>" (requires sign-out)
-  </message>
-
   <!-- 3G data Notifications -->
   <message name="IDS_3G_NOTIFICATION_MESSAGE" desc="Text of the 3G data notification telling that 3G data is enabled.">
     Google Chrome will use cellular data if you're not connected to another network.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e6ddb0ba..216c326 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6929,6 +6929,13 @@
         Enables an experiment to switch menu labels that use 'Save as...' to 'Download'.
       </message>
 
+      <message name="IDS_FLAGS_ENABLE_ENUMERATING_AUDIO_DEVICES_NAME" desc="Name of the flag to experimentally enable enumerating audio devices on ChromeOS.">
+        Experimentally enable enumerating audio devices.
+      </message>
+      <message name="IDS_FLAGS_ENABLE_ENUMERATING_AUDIO_DEVICES_DESCRIPTION" desc="Description of the flag that experimentally enables enumerating audio devices on ChromeOS.">
+        Experimentally enable the use of enumerating audio devices.
+      </message>
+
       <!-- WebRTC logs -->
       <message name="IDS_WEBRTC_LOGS_TITLE" desc="Title for the chrome://webrtc-logs page.">
         WebRTC logs
@@ -14811,10 +14818,10 @@
       <!-- Strings for ARC intent picker -->
       <if expr="chromeos">
         <message name="IDS_INTENT_PICKER_BUBBLE_VIEW_ALWAYS" desc="Message contained in a button shown to the user in order to choose and remember an app selection.">
-          ALWAYS
+          Always
         </message>
         <message name="IDS_INTENT_PICKER_BUBBLE_VIEW_JUST_ONCE" desc="Message contained in a button shown to the user in order to choose to open a URL in a given app only once.">
-          JUST ONCE
+          Just once
         </message>
         <message name="IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH" desc="Message contained in a label used as a header for a list of applications from which the user will pick one.">
           Open with
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 794e974..b78e7e0 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1509,7 +1509,10 @@
   <message name="IDS_SETTINGS_MANAGE_CERTIFICATES_DESCRIPTION" desc="Secondary, continued explanation of how to manage SSL certificates and settings in Privacy options">
     Manage HTTPS/SSL certificates and settings
   </message>
-  <message name="IDS_SETTINGS_SITE_SETTINGS" desc="Text of the button that takes a user to settings page thats allows users to modify site settings. Also the title of that settings page.">
+  <message name="IDS_SETTINGS_CONTENT_SETTINGS" desc="Text of the button that takes a user to settings page thats allows users to modify site settings. Also the title of that settings page.">
+    Content Settings
+  </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS" desc="Text of the button that takes a user to the enhanced settings page thats allows users to modify site settings. Also the title of that settings page.">
     Site Settings
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_DESCRIPTION" desc="Secondary, continued explanation of what content settings in Chrome do">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e9bf8c06..4fefb1d4 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -69,6 +69,7 @@
 #include "content/public/common/feature_h264_with_openh264_ffmpeg.h"
 #include "content/public/common/features.h"
 #include "gin/public/gin_features.h"
+#include "media/audio/audio_features.h"
 #include "media/base/media_switches.h"
 #include "media/midi/midi_switches.h"
 #include "ui/base/ui_base_switches.h"
@@ -611,7 +612,7 @@
     {IDS_FLAGS_WEBRTC_HW_ENCODING_NONE, switches::kDisableWebRtcHWEncoding,
      switches::kDisableWebRtcHWEncodingNone},
 };
-#endif
+#endif  // ENABLE_WEBRTC
 
 // RECORDING USER METRICS FOR FLAGS:
 // -----------------------------------------------------------------------------
@@ -753,7 +754,7 @@
      kOsLinux | kOsCrOS | kOsWin,
      ENABLE_DISABLE_VALUE_TYPE(switches::kEnableOverlayScrollbar,
                                switches::kDisableOverlayScrollbar)},
-#endif  // USE_AURA) || defined(OS_LINUX
+#endif  // USE_AURA || OS_LINUX
     {   // See http://crbug.com/120416 for how to remove this flag.
      "save-page-as-mhtml", IDS_FLAGS_SAVE_PAGE_AS_MHTML_NAME,
      IDS_FLAGS_SAVE_PAGE_AS_MHTML_DESCRIPTION, kOsMac | kOsWin | kOsLinux,
@@ -822,7 +823,7 @@
     {"enable-android-spellchecker", IDS_OPTIONS_ENABLE_SPELLCHECK,
      IDS_OPTIONS_ENABLE_ANDROID_SPELLCHECKER_DESCRIPTION, kOsAndroid,
      FEATURE_VALUE_TYPE(spellcheck::kAndroidSpellChecker)},
-#endif  // ENABLE_SPELLCHECK) && defined(OS_ANDROID
+#endif  // ENABLE_SPELLCHECK && OS_ANDROID
     {"enable-scroll-prediction", IDS_FLAGS_SCROLL_PREDICTION_NAME,
      IDS_FLAGS_SCROLL_PREDICTION_DESCRIPTION, kOsDesktop,
      SINGLE_VALUE_TYPE(switches::kEnableScrollPrediction)},
@@ -1219,7 +1220,7 @@
      IDS_FLAGS_RESET_APP_LIST_INSTALL_STATE_DESCRIPTION,
      kOsMac | kOsWin | kOsLinux,
      SINGLE_VALUE_TYPE(app_list::switches::kResetAppListInstallState)},
-#endif  // ENABLE_APP_LIST
+#endif  // BUILDFLAG(ENABLE_APP_LIST)
 #if defined(OS_ANDROID)
     {"enable-downloads-ui", IDS_FLAGS_ENABLE_DOWNLOADS_UI_NAME,
      IDS_FLAGS_ENABLE_DOWNLOADS_UI_DESCRIPTION, kOsAndroid,
@@ -1278,14 +1279,14 @@
     {"translate-2016q2-ui", IDS_FLAGS_TRANSLATE_2016Q2_UI_NAME,
      IDS_FLAGS_TRANSLATE_2016Q2_UI_DESCRIPTION, kOsCrOS | kOsWin | kOsLinux,
      FEATURE_VALUE_TYPE(translate::kTranslateUI2016Q2)},
-#endif  // OS_LINUX) || defined(OS_WIN) || defined(OS_CHROMEOS
+#endif  // OS_LINUX || OS_WIN || OS_CHROMEOS
     {"translate-lang-by-ulp", IDS_FLAGS_TRANSLATE_LANGUAGE_BY_ULP_NAME,
      IDS_FLAGS_TRANSLATE_LANGUAGE_BY_ULP_DESCRIPTION, kOsAll,
      FEATURE_VALUE_TYPE(translate::kTranslateLanguageByULP)},
 #if defined(OS_MACOSX)
     {"enable-native-notifications", IDS_NOTIFICATIONS_NATIVE_FLAG,
      IDS_NOTIFICATIONS_NATIVE_FLAG_DESCRIPTION, kOsMac,
-     SINGLE_VALUE_TYPE(switches::kEnableNativeNotifications)},
+     FEATURE_VALUE_TYPE(features::kNativeNotifications)},
 #endif  // OS_MACOSX
 #if defined(TOOLKIT_VIEWS)
     {"disable-views-rect-based-targeting",
@@ -1409,7 +1410,7 @@
      kOsDesktop,
      SINGLE_VALUE_TYPE(
          switches::kEnableMessageCenterAlwaysScrollUpUponNotificationRemoval)},
-#endif  // OS_ANDROID
+#endif  // !OS_ANDROID
     {"enable-md-policy-page", IDS_FLAGS_ENABLE_MATERIAL_DESIGN_POLICY_PAGE_NAME,
      IDS_FLAGS_ENABLE_MATERIAL_DESIGN_POLICY_PAGE_DESCRIPTION, kOsDesktop,
      SINGLE_VALUE_TYPE(switches::kEnableMaterialDesignPolicyPage)},
@@ -1535,7 +1536,7 @@
                                          "1",
                                          switches::kMediaRouter,
                                          "0")},
-#endif  // ENABLE_MEDIA_ROUTER) && !defined(OS_ANDROID
+#endif  // ENABLE_MEDIA_ROUTER && !OS_ANDROID
 // Since Drive Search is not available when app list is disabled, flag guard
 // enable-drive-search-in-chrome-launcher flag.
 #if BUILDFLAG(ENABLE_APP_LIST)
@@ -1545,7 +1546,7 @@
      ENABLE_DISABLE_VALUE_TYPE(
          app_list::switches::kEnableDriveSearchInChromeLauncher,
          app_list::switches::kDisableDriveSearchInChromeLauncher)},
-#endif  // ENABLE_APP_LIST
+#endif  // BUILDFLAG(ENABLE_APP_LIST)
 #if defined(OS_CHROMEOS)
     {"disable-mtp-write-support", IDS_FLAGS_MTP_WRITE_SUPPORT_NAME,
      IDS_FLAGS_MTP_WRITE_SUPPORT_DESCRIPTION, kOsCrOS,
@@ -1695,7 +1696,7 @@
      IDS_FLAGS_ENABLE_WEB_NOTIFICATION_CUSTOM_LAYOUTS_DESCRIPTION, kOsAndroid,
      ENABLE_DISABLE_VALUE_TYPE(switches::kEnableWebNotificationCustomLayouts,
                                switches::kDisableWebNotificationCustomLayouts)},
-#endif  // ENABLE_NOTIFICATIONS) && defined(OS_ANDROID
+#endif  // ENABLE_NOTIFICATIONS && OS_ANDROID
 #if defined(OS_WIN)
     {"enable-appcontainer", IDS_FLAGS_ENABLE_APPCONTAINER_NAME,
      IDS_FLAGS_ENABLE_APPCONTAINER_DESCRIPTION, kOsWin,
@@ -1709,7 +1710,7 @@
      ENABLE_DISABLE_VALUE_TYPE(
          autofill::switches::kEnableOfferUploadCreditCards,
          autofill::switches::kDisableOfferUploadCreditCards)},
-#endif  // TOOLKIT_VIEWS) || defined(OS_ANDROID
+#endif  // TOOLKIT_VIEWS || OS_ANDROID
 #if defined(OS_ANDROID)
     {"tab-management-experiment-type", IDS_FLAGS_HERB_PROTOTYPE_CHOICES_NAME,
      IDS_FLAGS_HERB_PROTOTYPE_CHOICES_DESCRIPTION, kOsAndroid,
@@ -1744,7 +1745,7 @@
      IDS_FLAGS_ENABLE_INPUT_IME_API_DESCRIPTION, kOsWin | kOsLinux,
      ENABLE_DISABLE_VALUE_TYPE(switches::kEnableInputImeAPI,
                                switches::kDisableInputImeAPI)},
-#endif  // OS_WIN) || defined(OS_LINUX
+#endif  // OS_WIN || OS_LINUX
     {"enable-origin-trials", IDS_FLAGS_ORIGIN_TRIALS_NAME,
      IDS_FLAGS_ORIGIN_TRIALS_DESCRIPTION, kOsAll,
      FEATURE_VALUE_TYPE(features::kOriginTrials)},
@@ -1856,8 +1857,7 @@
      IDS_FLAGS_WEBRTC_H264_WITH_OPENH264_FFMPEG_NAME,
      IDS_FLAGS_WEBRTC_H264_WITH_OPENH264_FFMPEG_DESCRIPTION, kOsDesktop,
      FEATURE_VALUE_TYPE(content::kWebRtcH264WithOpenH264FFmpeg)},
-#endif  // ENABLE_WEBRTC) && BUILDFLAG(RTC_USE_H264 &&
-        // !defined(MEDIA_DISABLE_FFMPEG)
+#endif  // ENABLE_WEBRTC && BUILDFLAG(RTC_USE_H264) && !MEDIA_DISABLE_FFMPEG
 #if defined(OS_ANDROID)
     {"ime-thread", IDS_FLAGS_IME_THREAD_NAME, IDS_FLAGS_IME_THREAD_DESCRIPTION,
      kOsAndroid, FEATURE_VALUE_TYPE(features::kImeThread)},
@@ -1905,13 +1905,13 @@
     {"enable-files-details-panel", IDS_FLAGS_ENABLE_FILES_DETAILS_PANEL_NAME,
      IDS_FLAGS_ENABLE_FILES_DETAILS_PANEL_DESCRIPTION, kOsCrOS,
      SINGLE_VALUE_TYPE(chromeos::switches::kEnableFilesDetailsPanel)},
-#endif  // defined(OS_CHROMEOS)
+#endif  // OS_CHROMEOS
 #if !defined(OS_ANDROID) && !defined(OS_IOS) && defined(GOOGLE_CHROME_BUILD)
     {"enable-google-branded-context-menu",
      IDS_FLAGS_GOOGLE_BRANDED_CONTEXT_MENU_NAME,
      IDS_FLAGS_GOOGLE_BRANDED_CONTEXT_MENU_DESCRIPTION, kOsDesktop,
      SINGLE_VALUE_TYPE(switches::kEnableGoogleBrandedContextMenu)},
-#endif  // OS_ANDROID) && !defined(OS_IOS) && defined(GOOGLE_CHROME_BUILD
+#endif  // !OS_ANDROID && !OS_IOS && GOOGLE_CHROME_BUILD
 #if defined(OS_MACOSX)
     {"enable-fullscreen-in-tab-detaching",
      IDS_FLAGS_TAB_DETACHING_IN_FULLSCREEN_NAME,
@@ -2021,12 +2021,10 @@
          chromeos::switches::kArcUseAuthEndpoint,
          "https://www-googleapis-staging.sandbox.google.com/oauth2/v4/"
          "ExchangeToken")},
-#endif  // OS_CHROMEOS
-#if defined(OS_CHROMEOS)
     {"arc-boot-completed-broadcast", IDS_FLAGS_ARC_BOOT_COMPLETED,
      IDS_FLAGS_ARC_BOOT_COMPLETED_DESCRIPTION, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kBootCompletedBroadcastFeature)},
-#endif
+#endif  // OS_CHROMEOS
 #if defined(OS_WIN)
     {"disable-winrt-midi-api", IDS_FLAGS_DISABLE_WINRT_MIDI_API_NAME,
      IDS_FLAGS_DISABLE_WINRT_MIDI_API_DESCRIPTION, kOsWin,
@@ -2042,12 +2040,18 @@
      IDS_FLAGS_EXPENSIVE_BACKGROUND_TIMER_THROTTLING_NAME,
      IDS_FLAGS_EXPENSIVE_BACKGROUND_TIMER_THROTTLING_DESCRIPTION, kOsAll,
      FEATURE_VALUE_TYPE(features::kExpensiveBackgroundTimerThrottling)},
-     {"security-chip", IDS_FLAGS_SECURITY_CHIP_NAME,
+    {"security-chip", IDS_FLAGS_SECURITY_CHIP_NAME,
      IDS_FLAGS_SECURITY_CHIP_DESCRIPTION, kOsDesktop,
      MULTI_VALUE_TYPE(kSecurityChipChoices)},
     {"security-chip-animation", IDS_FLAGS_SECURITY_CHIP_ANIMATION_NAME,
      IDS_FLAGS_SECURITY_CHIP_ANIMATION_DESCRIPTION, kOsDesktop,
      MULTI_VALUE_TYPE(kSecurityChipAnimationChoices)},
+#if defined(OS_CHROMEOS)
+    {"enumerate-audio-devices", IDS_FLAGS_ENABLE_ENUMERATING_AUDIO_DEVICES_NAME,
+     IDS_FLAGS_ENABLE_ENUMERATING_AUDIO_DEVICES_DESCRIPTION, kOsCrOS,
+     FEATURE_VALUE_TYPE(features::kEnumerateAudioDevices)},
+#endif  // OS_CHROMEOS
+
     // NOTE: Adding new command-line switches requires adding corresponding
     // entries to enum "LoginCustomFlags" in histograms.xml. See note in
     // histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 01449b0..aca30de6 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -1574,8 +1574,8 @@
 }
 
 - (void)delayedScreenParametersUpdate {
-  FOR_EACH_OBSERVER(ui::WorkAreaWatcherObserver, workAreaChangeObservers_,
-      WorkAreaChanged());
+  for (auto& observer : workAreaChangeObservers_)
+    observer.WorkAreaChanged();
 }
 
 - (BOOL)application:(NSApplication*)application
diff --git a/chrome/browser/background/background_application_list_model.cc b/chrome/browser/background/background_application_list_model.cc
index c37dae63..dfe9e251 100644
--- a/chrome/browser/background/background_application_list_model.cc
+++ b/chrome/browser/background/background_application_list_model.cc
@@ -330,8 +330,8 @@
 
 void BackgroundApplicationListModel::SendApplicationDataChangedNotifications(
     const Extension* extension) {
-  FOR_EACH_OBSERVER(Observer, observers_, OnApplicationDataChanged(extension,
-                                                                   profile_));
+  for (auto& observer : observers_)
+    observer.OnApplicationDataChanged(extension, profile_);
 }
 
 void BackgroundApplicationListModel::OnExtensionLoaded(
@@ -398,6 +398,7 @@
   }
   if (old_cursor != extensions_.end() || new_cursor != extensions.end()) {
     extensions_ = extensions;
-    FOR_EACH_OBSERVER(Observer, observers_, OnApplicationListChanged(profile_));
+    for (auto& observer : observers_)
+      observer.OnApplicationListChanged(profile_);
   }
 }
diff --git a/chrome/browser/background/background_contents.cc b/chrome/browser/background/background_contents.cc
index 7192ee7a..3d3d824d 100644
--- a/chrome/browser/background/background_contents.cc
+++ b/chrome/browser/background/background_contents.cc
@@ -99,9 +99,8 @@
       chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
       content::Source<Profile>(profile_),
       content::Details<BackgroundContents>(this));
-  FOR_EACH_OBSERVER(extensions::DeferredStartRenderHostObserver,
-                    deferred_start_render_host_observer_list_,
-                    OnDeferredStartRenderHostDestroyed(this));
+  for (auto& observer : deferred_start_render_host_observer_list_)
+    observer.OnDeferredStartRenderHostDestroyed(this);
 
   extension_host_delegate_->GetExtensionHostQueue()->Remove(this);
 }
@@ -174,17 +173,15 @@
 void BackgroundContents::DidStartLoading() {
   // BackgroundContents only loads once, so this can only be the first time it
   // has started loading.
-  FOR_EACH_OBSERVER(extensions::DeferredStartRenderHostObserver,
-                    deferred_start_render_host_observer_list_,
-                    OnDeferredStartRenderHostDidStartFirstLoad(this));
+  for (auto& observer : deferred_start_render_host_observer_list_)
+    observer.OnDeferredStartRenderHostDidStartFirstLoad(this);
 }
 
 void BackgroundContents::DidStopLoading() {
   // BackgroundContents only loads once, so this can only be the first time
   // it has stopped loading.
-  FOR_EACH_OBSERVER(extensions::DeferredStartRenderHostObserver,
-                    deferred_start_render_host_observer_list_,
-                    OnDeferredStartRenderHostDidStopFirstLoad(this));
+  for (auto& observer : deferred_start_render_host_observer_list_)
+    observer.OnDeferredStartRenderHostDidStopFirstLoad(this);
 }
 
 void BackgroundContents::Observe(int type,
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 449912a..87dd713 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3129,8 +3129,6 @@
       throttles.push_back(MergeSessionNavigationThrottle::Create(handle));
     }
 
-    // TODO(djacobo): Support incognito mode by showing an aditional dialog as a
-    // warning that the selected app is not in incognito mode.
     const arc::ArcAuthService* auth_service = arc::ArcAuthService::Get();
     if (auth_service && auth_service->IsArcEnabled() &&
         !handle->GetWebContents()->GetBrowserContext()->IsOffTheRecord()) {
diff --git a/chrome/browser/chrome_content_browser_manifest_overlay.json b/chrome/browser/chrome_content_browser_manifest_overlay.json
index 23481711..fc4c671 100644
--- a/chrome/browser/chrome_content_browser_manifest_overlay.json
+++ b/chrome/browser/chrome_content_browser_manifest_overlay.json
@@ -16,6 +16,7 @@
         "metrics::mojom::CallStackProfileCollector"
       ],
       "ash": [
+        "ash::mojom::LocaleNotificationController",
         "ash::mojom::ShelfController",
         "ash::mojom::SystemTray",
         "ash::mojom::SystemTrayClient"
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 7cb3aa43..7ec4616 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -834,8 +834,6 @@
     "login/ui/keyboard_driven_oobe_key_handler.h",
     "login/ui/lock_window.cc",
     "login/ui/lock_window.h",
-    "login/ui/lock_window_aura.cc",
-    "login/ui/lock_window_aura.h",
     "login/ui/login_display.cc",
     "login/ui/login_display.h",
     "login/ui/login_display_host.cc",
diff --git a/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc b/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
index e4bf0cc..35157fd4 100644
--- a/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
+++ b/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
@@ -70,15 +70,32 @@
                           int routing_id,
                           const GURL& url,
                           mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
-                          size_t selected_app_index,
+                          std::string selected_app_package,
                           ArcNavigationThrottle::CloseReason close_reason) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
+  size_t selected_app_index = handlers.size();
   // Make sure that the instance at least supports HandleUrl.
   auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance(
       "HandleUrl", kMinVersionForHandleUrl);
-  if (!instance || selected_app_index >= handlers.size())
+  if (!instance) {
     close_reason = ArcNavigationThrottle::CloseReason::ERROR;
+  } else if (close_reason ==
+                 ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED ||
+             close_reason ==
+                 ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED) {
+    // If the user selected an app to continue the navigation, confirm that the
+    // |package_name| matches a valid option and return the index.
+    for (size_t i = 0; i < handlers.size(); ++i) {
+      if (handlers[i]->package_name == selected_app_package) {
+        selected_app_index = i;
+        break;
+      }
+    }
+
+    if (selected_app_index == handlers.size())
+      close_reason = ArcNavigationThrottle::CloseReason::ERROR;
+  }
 
   switch (close_reason) {
     case ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED: {
@@ -127,15 +144,16 @@
     std::unique_ptr<ActivityIconLoader::ActivityToIconsMap> icons) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  using NameAndIcon = std::pair<std::string, gfx::Image>;
-  std::vector<NameAndIcon> app_info;
+  using AppInfo = arc::ArcNavigationThrottle::AppInfo;
+  std::vector<AppInfo> app_info;
 
   for (const auto& handler : handlers) {
     const ActivityIconLoader::ActivityName activity(handler->package_name,
                                                     handler->activity_name);
     const auto it = icons->find(activity);
     app_info.emplace_back(
-        handler->name, it != icons->end() ? it->second.icon20 : gfx::Image());
+        AppInfo(it != icons->end() ? it->second.icon20 : gfx::Image(),
+                handler->package_name, handler->name));
   }
 
   auto show_bubble_cb = base::Bind(ShowIntentPickerBubble());
diff --git a/chrome/browser/chromeos/arc/arc_navigation_throttle.cc b/chrome/browser/chromeos/arc/arc_navigation_throttle.cc
index d2d290b2..87f1acb4 100644
--- a/chrome/browser/chromeos/arc/arc_navigation_throttle.cc
+++ b/chrome/browser/chromeos/arc/arc_navigation_throttle.cc
@@ -173,7 +173,8 @@
           << "Chrome browser is selected as the preferred app for this URL: "
           << navigation_handle()->GetURL().spec();
     }
-    OnIntentPickerClosed(std::move(handlers), i,
+    std::string package_name = handlers[i]->package_name;
+    OnIntentPickerClosed(std::move(handlers), package_name,
                          CloseReason::PREFERRED_ACTIVITY_FOUND);
     return;
   }
@@ -212,7 +213,7 @@
     mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
     std::unique_ptr<ActivityIconLoader::ActivityToIconsMap> icons) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  std::vector<NameAndIcon> app_info;
+  std::vector<AppInfo> app_info;
 
   for (const auto& handler : handlers) {
     gfx::Image icon;
@@ -221,7 +222,8 @@
     const auto it = icons->find(activity);
 
     app_info.emplace_back(
-        handler->name, it != icons->end() ? it->second.icon20 : gfx::Image());
+        AppInfo(it != icons->end() ? it->second.icon20 : gfx::Image(),
+                handler->package_name, handler->name));
   }
 
   show_intent_picker_callback_.Run(
@@ -232,19 +234,34 @@
 
 void ArcNavigationThrottle::OnIntentPickerClosed(
     mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
-    size_t selected_app_index,
+    std::string selected_app_package,
     CloseReason close_reason) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   const GURL& url = navigation_handle()->GetURL();
   content::NavigationHandle* handle = navigation_handle();
-
   previous_user_action_ = close_reason;
 
   // Make sure that the instance at least supports HandleUrl.
   auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance(
       "HandleUrl", kMinVersionForHandleUrl);
-  if (!instance || selected_app_index >= handlers.size())
+  size_t selected_app_index = handlers.size();
+  if (!instance) {
     close_reason = CloseReason::ERROR;
+  } else if (close_reason == CloseReason::JUST_ONCE_PRESSED ||
+             close_reason == CloseReason::ALWAYS_PRESSED ||
+             close_reason == CloseReason::PREFERRED_ACTIVITY_FOUND) {
+    // Since we are selecting an app by its package name, we need to locate it
+    // on the |handlers| structure before sending the IPC to ARC.
+    for (size_t i = 0; i < handlers.size(); ++i) {
+      if (handlers[i]->package_name == selected_app_package) {
+        selected_app_index = i;
+        break;
+      }
+    }
+
+    if (selected_app_index == handlers.size())
+      close_reason = CloseReason::ERROR;
+  }
 
   switch (close_reason) {
     case CloseReason::ERROR:
@@ -272,8 +289,7 @@
               handlers[selected_app_index]->package_name)) {
         handle->Resume();
       } else {
-        instance->HandleUrl(url.spec(),
-                            handlers[selected_app_index]->package_name);
+        instance->HandleUrl(url.spec(), selected_app_package);
         handle->CancelDeferredNavigation(
             content::NavigationThrottle::CANCEL_AND_IGNORE);
         if (handle->GetWebContents()->GetController().IsInitialNavigation())
diff --git a/chrome/browser/chromeos/arc/arc_navigation_throttle.h b/chrome/browser/chromeos/arc/arc_navigation_throttle.h
index 188cadf..18be29a 100644
--- a/chrome/browser/chromeos/arc/arc_navigation_throttle.h
+++ b/chrome/browser/chromeos/arc/arc_navigation_throttle.h
@@ -47,11 +47,18 @@
   // ScrollView.
   enum { kMaxAppResults = 3 };
 
-  using NameAndIcon = std::pair<std::string, gfx::Image>;
-  using ShowIntentPickerCallback =
-      base::Callback<void(content::WebContents* web_contents,
-                          const std::vector<NameAndIcon>& app_info,
-                          const base::Callback<void(size_t, CloseReason)>& cb)>;
+  struct AppInfo {
+    explicit AppInfo(gfx::Image img, std::string package, std::string activity)
+        : icon(img), package_name(package), activity_name(activity) {}
+    gfx::Image icon;
+    std::string package_name;
+    std::string activity_name;
+  };
+
+  using ShowIntentPickerCallback = base::Callback<void(
+      content::WebContents* web_contents,
+      const std::vector<AppInfo>& app_info,
+      const base::Callback<void(std::string, CloseReason)>& cb)>;
   ArcNavigationThrottle(content::NavigationHandle* navigation_handle,
                         const ShowIntentPickerCallback& show_intent_picker_cb);
   ~ArcNavigationThrottle() override;
@@ -71,7 +78,7 @@
       mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
       std::unique_ptr<ActivityIconLoader::ActivityToIconsMap> icons);
   void OnIntentPickerClosed(mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
-                            size_t selected_app_index,
+                            std::string selected_app_package,
                             CloseReason close_reason);
   // A callback object that allow us to display an IntentPicker when Run() is
   // executed, it also allow us to report the user's selection back to
diff --git a/chrome/browser/chromeos/arc/arc_support_host.cc b/chrome/browser/chromeos/arc/arc_support_host.cc
index 7867122..214c0554 100644
--- a/chrome/browser/chromeos/arc/arc_support_host.cc
+++ b/chrome/browser/chromeos/arc/arc_support_host.cc
@@ -233,7 +233,7 @@
       "learnMoreLocationServices",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_LOCATION_SERVICES));
   loadtime_data->SetString(
-      "learnMoreClose",
+      "overlayClose",
       l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_LEARN_MORE_CLOSE));
 
   const std::string& app_locale = g_browser_process->GetApplicationLocale();
diff --git a/chrome/browser/chromeos/locale_change_guard.cc b/chrome/browser/chromeos/locale_change_guard.cc
index 1f26ce5..13e1af9e 100644
--- a/chrome/browser/chromeos/locale_change_guard.cc
+++ b/chrome/browser/chromeos/locale_change_guard.cc
@@ -6,8 +6,6 @@
 
 #include <algorithm>
 
-#include "ash/common/system/tray/system_tray_notifier.h"
-#include "ash/common/wm_shell.h"
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
@@ -17,15 +15,17 @@
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/common/pref_names.h"
-#include "chrome/grit/generated_resources.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/user_metrics.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/l10n/l10n_util.h"
 
 using base::UserMetricsAction;
@@ -63,6 +63,23 @@
                  content::NotificationService::AllBrowserContextsAndSources());
 }
 
+void LocaleChangeGuard::ConnectToLocaleNotificationController() {
+  content::ServiceManagerConnection* connection =
+      content::ServiceManagerConnection::GetForProcess();
+  service_manager::Connector* connector =
+      connection ? connection->GetConnector() : nullptr;
+  // Unit tests may not have a connector.
+  if (!connector)
+    return;
+
+  if (chrome::IsRunningInMash()) {
+    connector->ConnectToInterface("service:ash", &notification_controller_);
+  } else {
+    connector->ConnectToInterface("service:content_browser",
+                                  &notification_controller_);
+  }
+}
+
 void LocaleChangeGuard::RevertLocaleChange() {
   if (profile_ == NULL ||
       from_locale_.empty() ||
@@ -176,8 +193,23 @@
     PrepareChangingLocale(from_locale, to_locale);
   }
 
-  ash::WmShell::Get()->system_tray_notifier()->NotifyLocaleChanged(
-      this, cur_locale, from_locale_, to_locale_);
+  if (!notification_controller_)
+    ConnectToLocaleNotificationController();
+
+  notification_controller_->OnLocaleChanged(
+      cur_locale, from_locale_, to_locale_,
+      base::Bind(&LocaleChangeGuard::OnResult, AsWeakPtr()));
+}
+
+void LocaleChangeGuard::OnResult(ash::mojom::LocaleNotificationResult result) {
+  switch (result) {
+    case ash::mojom::LocaleNotificationResult::ACCEPT:
+      AcceptLocaleChange();
+      break;
+    case ash::mojom::LocaleNotificationResult::REVERT:
+      RevertLocaleChange();
+      break;
+  }
 }
 
 void LocaleChangeGuard::AcceptLocaleChange() {
@@ -211,20 +243,6 @@
     from_locale_ = from_locale;
   if (!to_locale.empty())
     to_locale_ = to_locale;
-
-  if (!from_locale_.empty() && !to_locale_.empty()) {
-    base::string16 from = l10n_util::GetDisplayNameForLocale(
-        from_locale_, cur_locale, true);
-    base::string16 to = l10n_util::GetDisplayNameForLocale(
-        to_locale_, cur_locale, true);
-
-    title_text_ = l10n_util::GetStringUTF16(
-        IDS_OPTIONS_SETTINGS_SECTION_TITLE_LANGUAGE);
-    message_text_ = l10n_util::GetStringFUTF16(
-        IDS_LOCALE_CHANGE_MESSAGE, from, to);
-    revert_link_text_ = l10n_util::GetStringFUTF16(
-        IDS_LOCALE_CHANGE_REVERT_MESSAGE, from);
-  }
 }
 
 // static
diff --git a/chrome/browser/chromeos/locale_change_guard.h b/chrome/browser/chromeos/locale_change_guard.h
index 3d53f92..8a6c16f 100644
--- a/chrome/browser/chromeos/locale_change_guard.h
+++ b/chrome/browser/chromeos/locale_change_guard.h
@@ -10,7 +10,7 @@
 #include <memory>
 #include <string>
 
-#include "ash/common/system/locale/locale_observer.h"
+#include "ash/public/interfaces/locale.mojom.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
 #include "base/lazy_instance.h"
@@ -32,16 +32,11 @@
 // (based on synchronized user preference).  If so: shows notification that
 // allows user to revert change.
 class LocaleChangeGuard : public content::NotificationObserver,
-                          public ash::LocaleObserver::Delegate,
                           public base::SupportsWeakPtr<LocaleChangeGuard> {
  public:
   explicit LocaleChangeGuard(Profile* profile);
   ~LocaleChangeGuard() override;
 
-  // ash::LocaleChangeDelegate implementation.
-  void AcceptLocaleChange() override;
-  void RevertLocaleChange() override;
-
   // Called just before changing locale.
   void PrepareChangingLocale(
       const std::string& from_locale, const std::string& to_locale);
@@ -55,9 +50,15 @@
   FRIEND_TEST_ALL_PREFIXES(LocaleChangeGuardTest,
                            ShowNotificationLocaleChangedList);
 
+  void ConnectToLocaleNotificationController();
+
   void RevertLocaleChangeCallback(const base::ListValue* list);
   void Check();
 
+  void OnResult(ash::mojom::LocaleNotificationResult result);
+  void AcceptLocaleChange();
+  void RevertLocaleChange();
+
   // content::NotificationObserver implementation.
   void Observe(int type,
                const content::NotificationSource& source,
@@ -70,6 +71,9 @@
   static const char* const* GetSkipShowNotificationLanguagesForTesting();
   static size_t GetSkipShowNotificationLanguagesSizeForTesting();
 
+  // Ash's mojom::LocaleNotificationController used to display notifications.
+  ash::mojom::LocaleNotificationControllerPtr notification_controller_;
+
   std::string from_locale_;
   std::string to_locale_;
   Profile* profile_;
@@ -78,12 +82,7 @@
   bool main_frame_loaded_;
   content::NotificationRegistrar registrar_;
 
-  // We want to show locale change notification in previous language however
-  // we cannot directly load strings for non-current locale.  So we cache
-  // messages before locale change.
-  base::string16 title_text_;
-  base::string16 message_text_;
-  base::string16 revert_link_text_;
+  DISALLOW_COPY_AND_ASSIGN(LocaleChangeGuard);
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
index 630fb5d..5b64189 100644
--- a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
@@ -80,7 +80,7 @@
   gfx::Rect bounds = display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
 
   lock_time_ = base::TimeTicks::Now();
-  LockWindow* lock_window = LockWindow::Create();
+  auto* lock_window = new LockWindow();
   lock_window->set_observer(this);
   lock_window->set_initially_focused_view(this);
   lock_window_ = lock_window->GetWidget();
diff --git a/chrome/browser/chromeos/login/ui/lock_window.cc b/chrome/browser/chromeos/login/ui/lock_window.cc
index 5a609c8..c9a4392 100644
--- a/chrome/browser/chromeos/login/ui/lock_window.cc
+++ b/chrome/browser/chromeos/login/ui/lock_window.cc
@@ -4,11 +4,47 @@
 
 #include "chrome/browser/chromeos/login/ui/lock_window.h"
 
-#include <cstddef>
+#include "ash/common/shell_window_ids.h"
+#include "ash/shell.h"
+#include "ui/aura/window.h"
+#include "ui/events/gestures/gesture_recognizer.h"
 
 namespace chromeos {
 
-LockWindow::LockWindow() : observer_(NULL), initially_focused_view_(NULL) {
+LockWindow::LockWindow() {
+  ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(nullptr);
+
+  views::Widget::InitParams params(
+      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+  params.delegate = this;
+  params.show_state = ui::SHOW_STATE_FULLSCREEN;
+  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+  params.parent =
+      ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
+                               ash::kShellWindowId_LockScreenContainer);
+  views::Widget::Init(params);
+  SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
+}
+
+LockWindow::~LockWindow() {}
+
+void LockWindow::Grab() {
+  // We already have grab from the lock screen container, just call the ready
+  // callback immediately.
+  if (observer_)
+    observer_->OnLockWindowReady();
+}
+
+views::Widget* LockWindow::GetWidget() {
+  return this;
+}
+
+const views::Widget* LockWindow::GetWidget() const {
+  return this;
+}
+
+views::View* LockWindow::GetInitiallyFocusedView() {
+  return initially_focused_view_;
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/lock_window.h b/chrome/browser/chromeos/login/ui/lock_window.h
index b3076a2..9fbc0fe 100644
--- a/chrome/browser/chromeos/login/ui/lock_window.h
+++ b/chrome/browser/chromeos/login/ui/lock_window.h
@@ -6,6 +6,8 @@
 #define CHROME_BROWSER_CHROMEOS_LOGIN_UI_LOCK_WINDOW_H_
 
 #include "base/macros.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
 
 namespace views {
 class View;
@@ -14,9 +16,8 @@
 
 namespace chromeos {
 
-// This is the interface which lock windows used for the WebUI screen locker
-// implement.
-class LockWindow {
+// Shows the widget for the WebUI screen locker.
+class LockWindow : public views::Widget, public views::WidgetDelegate {
  public:
   // This class provides an interface for the lock window to notify an observer
   // about its status.
@@ -28,36 +29,35 @@
   };
 
   LockWindow();
+  ~LockWindow() override;
 
   // Attempt to grab inputs on the webview, the actual view displaying the lock
   // screen WebView.
-  virtual void Grab() = 0;
-
-  // Returns the actual widget for the lock window.
-  virtual views::Widget* GetWidget() = 0;
+  void Grab();
 
   // Sets the observer class which is notified on lock window events.
-  void set_observer(Observer* observer) {
-    observer_ = observer;
-  }
+  void set_observer(Observer* observer) { observer_ = observer; }
 
   // Sets the view which should be initially focused.
   void set_initially_focused_view(views::View* view) {
     initially_focused_view_ = view;
   }
 
-  // Creates an instance of the platform specific lock window.
-  static LockWindow* Create();
-
- protected:
-  // The observer's OnLockWindowReady method will be called when the lock
-  // window has finished all initialization.
-  Observer* observer_;
-
-  // The view which should be initially focused.
-  views::View* initially_focused_view_;
+  // views::WidgetDelegate:
+  views::Widget* GetWidget() override;
+  const views::Widget* GetWidget() const override;
 
  private:
+  // views::WidgetDelegate:
+  views::View* GetInitiallyFocusedView() override;
+
+  // The observer's OnLockWindowReady method will be called when the lock
+  // window has finished all initialization.
+  Observer* observer_ = nullptr;
+
+  // The view which should be initially focused.
+  views::View* initially_focused_view_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(LockWindow);
 };
 
diff --git a/chrome/browser/chromeos/login/ui/lock_window_aura.cc b/chrome/browser/chromeos/login/ui/lock_window_aura.cc
deleted file mode 100644
index bff36c4..0000000
--- a/chrome/browser/chromeos/login/ui/lock_window_aura.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/login/ui/lock_window_aura.h"
-
-#include "ash/common/shell_window_ids.h"
-#include "ash/shell.h"
-#include "base/command_line.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_event_dispatcher.h"
-
-namespace chromeos {
-
-LockWindow* LockWindow::Create() {
-  LockWindowAura* lock_window = new LockWindowAura();
-  // Cancel existing touch events when screen is locked.
-  ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(nullptr);
-  return lock_window;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// LockWindow implementation:
-void LockWindowAura::Grab() {
-  // We already have grab from the lock screen container, just call the ready
-  // callback immediately.
-  if (observer_)
-    observer_->OnLockWindowReady();
-}
-
-views::Widget* LockWindowAura::GetWidget() {
-  return this;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// views::WidgetDelegate implementation:
-views::View* LockWindowAura::GetInitiallyFocusedView() {
-  return initially_focused_view_;
-}
-
-const views::Widget* LockWindowAura::GetWidget() const {
-  return this;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// LockWindowAura private:
-LockWindowAura::LockWindowAura() {
-  Init();
-}
-
-LockWindowAura::~LockWindowAura() {
-}
-
-void LockWindowAura::Init() {
-  views::Widget::InitParams params(
-      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
-  params.delegate = this;
-  params.show_state = ui::SHOW_STATE_FULLSCREEN;
-  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
-  // TODO(oshima): move the lock screen harness to ash.
-  params.parent =
-      ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
-                               ash::kShellWindowId_LockScreenContainer);
-  views::Widget::Init(params);
-  SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/ui/lock_window_aura.h b/chrome/browser/chromeos/login/ui/lock_window_aura.h
deleted file mode 100644
index 548ae162..0000000
--- a/chrome/browser/chromeos/login/ui/lock_window_aura.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_UI_LOCK_WINDOW_AURA_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_UI_LOCK_WINDOW_AURA_H_
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "chrome/browser/chromeos/login/ui/lock_window.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/widget/widget_delegate.h"
-
-namespace chromeos {
-
-class LockWindowAura : public views::Widget,
-                       public views::WidgetDelegate,
-                       public LockWindow {
- public:
-  // LockWindow implementation:
-  void Grab() override;
-  views::Widget* GetWidget() override;
-
-  // views::WidgetDelegate implementation:
-  views::View* GetInitiallyFocusedView() override;
-  const views::Widget* GetWidget() const override;
-
- private:
-  friend class LockWindow;
-
-  LockWindowAura();
-  ~LockWindowAura() override;
-
-  // Initialize the Aura lock window.
-  void Init();
-
-  DISALLOW_COPY_AND_ASSIGN(LockWindowAura);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_UI_LOCK_WINDOW_AURA_H_
diff --git a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager_browsertest.cc b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager_browsertest.cc
index 79386a8..ea2f17e7 100644
--- a/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager_browsertest.cc
+++ b/chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager_browsertest.cc
@@ -792,7 +792,9 @@
       wallpaper_manager_test_utils::kLargeGuestWallpaperColor));
 }
 
-IN_PROC_BROWSER_TEST_F(WallpaperManagerBrowserTest, SmallChildWallpaper) {
+// Fails (crbug/657180)
+IN_PROC_BROWSER_TEST_F(WallpaperManagerBrowserTest,
+                       DISABLED_SmallChildWallpaper) {
   if (!ash::test::AshTestHelper::SupportsMultipleDisplays())
     return;
   CreateCmdlineWallpapers();
@@ -805,7 +807,9 @@
       wallpaper_manager_test_utils::kSmallChildWallpaperColor));
 }
 
-IN_PROC_BROWSER_TEST_F(WallpaperManagerBrowserTest, LargeChildWallpaper) {
+// Fails (crbug/657180)
+IN_PROC_BROWSER_TEST_F(WallpaperManagerBrowserTest,
+                       DISABLED_LargeChildWallpaper) {
   if (!ash::test::AshTestHelper::SupportsMultipleDisplays())
     return;
 
diff --git a/chrome/browser/command_updater.cc b/chrome/browser/command_updater.cc
index 950a17e..5321e01 100644
--- a/chrome/browser/command_updater.cc
+++ b/chrome/browser/command_updater.cc
@@ -73,8 +73,8 @@
   if (command->enabled == enabled)
     return;  // Nothing to do.
   command->enabled = enabled;
-  FOR_EACH_OBSERVER(CommandObserver, command->observers,
-                    EnabledStateChangedForCommand(id, enabled));
+  for (auto& observer : command->observers)
+    observer.EnabledStateChangedForCommand(id, enabled);
 }
 
 CommandUpdater::Command* CommandUpdater::GetCommand(int id, bool create) {
diff --git a/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc b/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
index 8c27f28..4beaf8cf 100644
--- a/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
+++ b/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
@@ -211,6 +211,8 @@
                      &Delegate::RemovePreference, delegate);
   d->RegisterHandler("clearPreferences",
                      &Delegate::ClearPreferences, delegate);
+  d->RegisterHandlerWithCallback("reattach",
+                                 &Delegate::Reattach, delegate);
   d->RegisterHandler("readyForTest",
                      &Delegate::ReadyForTest, delegate);
   return d;
diff --git a/chrome/browser/devtools/devtools_embedder_message_dispatcher.h b/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
index 141392fa..fd8665b 100644
--- a/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
+++ b/chrome/browser/devtools/devtools_embedder_message_dispatcher.h
@@ -88,6 +88,7 @@
     virtual void SendJsonRequest(const DispatchCallback& callback,
                                  const std::string& browser_id,
                                  const std::string& url) = 0;
+    virtual void Reattach(const DispatchCallback& callback) = 0;
     virtual void ReadyForTest() = 0;
   };
 
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index fd3f0dd..4f8473b3 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -402,7 +402,7 @@
       delegate_(new DefaultBindingsDelegate(web_contents_)),
       devices_updates_enabled_(false),
       frontend_loaded_(false),
-      reattaching_(false),
+      reloading_(false),
       weak_factory_(this) {
   g_instances.Get().push_back(this);
   frontend_contents_observer_.reset(new FrontendWebContentsObserver(this));
@@ -856,6 +856,13 @@
   update.Get()->Clear();
 }
 
+void DevToolsUIBindings::Reattach(const DispatchCallback& callback) {
+  DCHECK(agent_host_.get());
+  agent_host_->DetachClient(this);
+  agent_host_->AttachClient(this);
+  callback.Run(nullptr);
+}
+
 void DevToolsUIBindings::ReadyForTest() {
   delegate_->ReadyForTest();
 }
@@ -1106,9 +1113,10 @@
   agent_host_->ForceAttachClient(this);
 }
 
-void DevToolsUIBindings::Reattach() {
+void DevToolsUIBindings::Reload() {
   DCHECK(agent_host_.get());
-  reattaching_ = true;
+  reloading_ = true;
+  web_contents_->GetController().Reload(false);
 }
 
 void DevToolsUIBindings::Detach() {
@@ -1145,9 +1153,9 @@
 }
 
 void DevToolsUIBindings::DocumentAvailableInMainFrame() {
-  if (!reattaching_)
+  if (!reloading_)
     return;
-  reattaching_ = false;
+  reloading_ = false;
   agent_host_->DetachClient(this);
   agent_host_->AttachClient(this);
 }
diff --git a/chrome/browser/devtools/devtools_ui_bindings.h b/chrome/browser/devtools/devtools_ui_bindings.h
index 8888234..22939c4 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.h
+++ b/chrome/browser/devtools/devtools_ui_bindings.h
@@ -76,7 +76,7 @@
                           const base::Value* arg2,
                           const base::Value* arg3);
   void AttachTo(const scoped_refptr<content::DevToolsAgentHost>& agent_host);
-  void Reattach();
+  void Reload();
   void Detach();
   bool IsAttachedTo(content::DevToolsAgentHost* agent_host);
 
@@ -145,6 +145,7 @@
                      const std::string& value) override;
   void RemovePreference(const std::string& name) override;
   void ClearPreferences() override;
+  void Reattach(const DispatchCallback& callback) override;
   void ReadyForTest() override;
 
   // net::URLFetcherDelegate overrides.
@@ -221,7 +222,7 @@
 
   bool devices_updates_enabled_;
   bool frontend_loaded_;
-  bool reattaching_;
+  bool reloading_;
   std::unique_ptr<DevToolsTargetsUIHandler> remote_targets_handler_;
   std::unique_ptr<PortForwardingStatusSerializer> port_status_serializer_;
   PrefChangeRegistrar pref_change_registrar_;
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index 0f93220c..f8903d9 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -943,11 +943,7 @@
     return inspected_web_contents ?
         inspected_web_contents->OpenURL(params) : NULL;
   }
-
-  bindings_->Reattach();
-
-  content::NavigationController::LoadURLParams load_url_params(params.url);
-  main_web_contents_->GetController().LoadURLWithParams(load_url_params);
+  bindings_->Reload();
   return main_web_contents_;
 }
 
diff --git a/chrome/browser/media/android/router/media_router_android.cc b/chrome/browser/media/android/router/media_router_android.cc
index 62f8fee20..f7d5a11 100644
--- a/chrome/browser/media/android/router/media_router_android.cc
+++ b/chrome/browser/media/android/router/media_router_android.cc
@@ -355,8 +355,8 @@
   auto it = sinks_observers_.find(source_urn);
   if (it != sinks_observers_.end()) {
     // TODO(imcheng): Pass origins to OnSinksUpdated (crbug.com/594858).
-    FOR_EACH_OBSERVER(MediaSinksObserver, *it->second,
-                      OnSinksUpdated(sinks_converted, std::vector<GURL>()));
+    for (auto& observer : *it->second)
+      observer.OnSinksUpdated(sinks_converted, std::vector<GURL>());
   }
 }
 
@@ -385,8 +385,8 @@
   route_requests_.Remove(jroute_request_id);
 
   active_routes_.push_back(route);
-  FOR_EACH_OBSERVER(MediaRoutesObserver, routes_observers_,
-      OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>()));
+  for (auto& observer : routes_observers_)
+    observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>());
 }
 
 void MediaRouterAndroid::OnRouteRequestError(
@@ -419,8 +419,8 @@
       break;
     }
 
-  FOR_EACH_OBSERVER(MediaRoutesObserver, routes_observers_,
-      OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>()));
+  for (auto& observer : routes_observers_)
+    observer.OnRoutesUpdated(active_routes_, std::vector<MediaRoute::Id>());
   NotifyPresentationConnectionStateChange(
       route_id, content::PRESENTATION_CONNECTION_STATE_TERMINATED);
 }
@@ -462,8 +462,8 @@
   std::vector<RouteMessage> messages(1);
   messages.front().type = RouteMessage::TEXT;
   messages.front().text = ConvertJavaStringToUTF8(env, jmessage);
-  FOR_EACH_OBSERVER(RouteMessageObserver, *observer_list,
-                    OnMessagesReceived(messages));
+  for (auto& observer : *observer_list)
+    observer.OnMessagesReceived(messages);
 }
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/issue_manager.cc b/chrome/browser/media/router/issue_manager.cc
index d3cb9d4..53d74c8c 100644
--- a/chrome/browser/media/router/issue_manager.cc
+++ b/chrome/browser/media/router/issue_manager.cc
@@ -95,8 +95,8 @@
   const Issue* new_top_issue = nullptr;
 
   if (issues_.empty()) {
-    FOR_EACH_OBSERVER(IssuesObserver, issues_observers_,
-                      OnIssueUpdated(new_top_issue));
+    for (auto& observer : issues_observers_)
+      observer.OnIssueUpdated(new_top_issue);
     return;
   }
 
@@ -114,8 +114,8 @@
   // If we've found a new top issue, then report it via the observer.
   if (new_top_issue->id() != top_issue_id_) {
     top_issue_id_ = new_top_issue->id();
-    FOR_EACH_OBSERVER(IssuesObserver, issues_observers_,
-                      OnIssueUpdated(new_top_issue));
+    for (auto& observer : issues_observers_)
+      observer.OnIssueUpdated(new_top_issue);
   }
 }
 
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 0155bef..9517982 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -201,9 +201,10 @@
     DVLOG_WITH_INSTANCE(1)
         << "Received sink list without any active observers: " << media_source;
   } else {
-    FOR_EACH_OBSERVER(
-        MediaSinksObserver, sinks_query->observers,
-        OnSinksUpdated(sinks_query->cached_sink_list, sinks_query->origins));
+    for (auto& observer : sinks_query->observers) {
+      observer.OnSinksUpdated(sinks_query->cached_sink_list,
+                              sinks_query->origins);
+    }
   }
 }
 
@@ -227,9 +228,8 @@
   for (size_t i = 0; i < routes.size(); ++i)
     routes_converted.push_back(routes[i].To<MediaRoute>());
 
-  FOR_EACH_OBSERVER(
-      MediaRoutesObserver, it->second->observers,
-      OnRoutesUpdated(routes_converted, joinable_route_ids));
+  for (auto& observer : it->second->observers)
+    observer.OnRoutesUpdated(routes_converted, joinable_route_ids);
 }
 
 void MediaRouterMojoImpl::RouteResponseReceived(
@@ -710,8 +710,8 @@
     return;
   }
 
-  FOR_EACH_OBSERVER(RouteMessageObserver, *observer_list,
-                    OnMessagesReceived(messages));
+  for (auto& observer : *observer_list)
+    observer.OnMessagesReceived(messages);
 }
 
 void MediaRouterMojoImpl::OnSinkAvailabilityUpdated(
diff --git a/chrome/browser/media/router/presentation_service_delegate_impl.cc b/chrome/browser/media/router/presentation_service_delegate_impl.cc
index 415974da..0a40a5b 100644
--- a/chrome/browser/media/router/presentation_service_delegate_impl.cc
+++ b/chrome/browser/media/router/presentation_service_delegate_impl.cc
@@ -622,9 +622,8 @@
     return;
 
   default_presentation_request_.reset();
-  FOR_EACH_OBSERVER(
-      PresentationServiceDelegateImpl::DefaultPresentationRequestObserver,
-      default_presentation_request_observers_, OnDefaultPresentationRemoved());
+  for (auto& observer : default_presentation_request_observers_)
+    observer.OnDefaultPresentationRemoved();
 }
 
 bool PresentationFrameManager::IsMainFrame(
@@ -641,10 +640,8 @@
 
   default_presentation_request_.reset(
       new PresentationRequest(default_presentation_request));
-  FOR_EACH_OBSERVER(
-      PresentationServiceDelegateImpl::DefaultPresentationRequestObserver,
-      default_presentation_request_observers_,
-      OnDefaultPresentationChanged(*default_presentation_request_));
+  for (auto& observer : default_presentation_request_observers_)
+    observer.OnDefaultPresentationChanged(*default_presentation_request_);
 }
 
 void PresentationFrameManager::SetMediaRouterForTest(MediaRouter* router) {
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
index 8ea8d36cb..e4177e3 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -322,14 +322,14 @@
 
 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
   MediaStreamDevices devices = GetAudioCaptureDevices();
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnUpdateAudioDevices(devices));
+  for (auto& observer : observers_)
+    observer.OnUpdateAudioDevices(devices);
 }
 
 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
   MediaStreamDevices devices = GetVideoCaptureDevices();
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnUpdateVideoDevices(devices));
+  for (auto& observer : observers_)
+    observer.OnUpdateVideoDevices(devices);
 }
 
 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
@@ -358,19 +358,18 @@
   }
 #endif
 
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnRequestUpdate(render_process_id,
-                                    render_frame_id,
-                                    stream_type,
-                                    state));
+  for (auto& observer : observers_) {
+    observer.OnRequestUpdate(render_process_id, render_frame_id, stream_type,
+                             state);
+  }
 }
 
 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
     int render_process_id,
     int render_frame_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnCreatingAudioStream(render_process_id, render_frame_id));
+  for (auto& observer : observers_)
+    observer.OnCreatingAudioStream(render_process_id, render_frame_id);
 }
 
 bool MediaCaptureDevicesDispatcher::IsInsecureCapturingInProgress(
diff --git a/chrome/browser/media_galleries/media_galleries_preferences.cc b/chrome/browser/media_galleries/media_galleries_preferences.cc
index a32a4bb..db859f5 100644
--- a/chrome/browser/media_galleries/media_galleries_preferences.cc
+++ b/chrome/browser/media_galleries/media_galleries_preferences.cc
@@ -638,9 +638,8 @@
       InitFromPrefs();
       MediaGalleryPrefId pref_id;
       if (GetPrefId(*dict, &pref_id)) {
-        FOR_EACH_OBSERVER(GalleryChangeObserver,
-                          gallery_change_observers_,
-                          OnGalleryInfoUpdated(this, pref_id));
+        for (auto& observer : gallery_change_observers_)
+          observer.OnGalleryInfoUpdated(this, pref_id);
       }
       return true;
     }
@@ -997,8 +996,8 @@
     update.reset();
 
     InitFromPrefs();
-    FOR_EACH_OBSERVER(GalleryChangeObserver, gallery_change_observers_,
-                      OnGalleryInfoUpdated(this, *pref_id_it));
+    for (auto& observer : gallery_change_observers_)
+      observer.OnGalleryInfoUpdated(this, *pref_id_it);
     return *pref_id_it;
   }
 
@@ -1029,9 +1028,8 @@
     list->Append(CreateGalleryPrefInfoDictionary(gallery_info));
   }
   InitFromPrefs();
-  FOR_EACH_OBSERVER(GalleryChangeObserver,
-                    gallery_change_observers_,
-                    OnGalleryAdded(this, gallery_info.pref_id));
+  for (auto& observer : gallery_change_observers_)
+    observer.OnGalleryAdded(this, gallery_info.pref_id);
 
   return gallery_info.pref_id;
 }
@@ -1105,9 +1103,8 @@
   for (std::vector<MediaGalleryPrefId>::iterator iter = pref_ids.begin();
        iter != pref_ids.end();
        ++iter) {
-    FOR_EACH_OBSERVER(GalleryChangeObserver,
-                      gallery_change_observers_,
-                      OnGalleryInfoUpdated(this, *iter));
+    for (auto& observer : gallery_change_observers_)
+      observer.OnGalleryInfoUpdated(this, *iter);
   }
 }
 
@@ -1181,9 +1178,8 @@
       update.reset(NULL);  // commits the update.
 
       InitFromPrefs();
-      FOR_EACH_OBSERVER(GalleryChangeObserver,
-                        gallery_change_observers_,
-                        OnGalleryRemoved(this, id));
+      for (auto& observer : gallery_change_observers_)
+        observer.OnGalleryRemoved(this, id);
       return;
     }
   }
@@ -1285,14 +1281,13 @@
     if (!SetGalleryPermissionInPrefs(extension.id(), pref_id, has_permission))
       return false;
   }
-  if (has_permission)
-    FOR_EACH_OBSERVER(GalleryChangeObserver,
-                      gallery_change_observers_,
-                      OnPermissionAdded(this, extension.id(), pref_id));
-  else
-    FOR_EACH_OBSERVER(GalleryChangeObserver,
-                      gallery_change_observers_,
-                      OnPermissionRemoved(this, extension.id(), pref_id));
+  if (has_permission) {
+    for (auto& observer : gallery_change_observers_)
+      observer.OnPermissionAdded(this, extension.id(), pref_id);
+  } else {
+    for (auto& observer : gallery_change_observers_)
+      observer.OnPermissionRemoved(this, extension.id(), pref_id);
+  }
   return true;
 }
 
diff --git a/chrome/browser/notifications/notification_display_service_factory.cc b/chrome/browser/notifications/notification_display_service_factory.cc
index 1caf6574..8b40bcd 100644
--- a/chrome/browser/notifications/notification_display_service_factory.cc
+++ b/chrome/browser/notifications/notification_display_service_factory.cc
@@ -11,7 +11,7 @@
 #include "chrome/browser/notifications/notification_ui_manager.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_features.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
@@ -39,8 +39,8 @@
 // Selection of the implementation works as follows:
 //   - Android always uses the NativeNotificationDisplayService.
 //   - Mac uses the MessageCenterDisplayService by default, but can use the
-//     NativeNotificationDisplayService by using the chrome://flags or the
-//     --enable-native-notifications command line flag.
+//     NativeNotificationDisplayService by using the chrome://flags or via
+//     the --enable-features=NativeNotifications command line flag.
 //   - All other platforms always use the MessageCenterDisplayService.
 KeyedService* NotificationDisplayServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
@@ -49,8 +49,7 @@
       Profile::FromBrowserContext(context),
       g_browser_process->notification_platform_bridge());
 #elif defined(OS_MACOSX)
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kEnableNativeNotifications)) {
+  if (base::FeatureList::IsEnabled(features::kNativeNotifications)) {
     return new NativeNotificationDisplayService(
         Profile::FromBrowserContext(context),
         g_browser_process->notification_platform_bridge());
diff --git a/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc b/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
index 384a4c0b..bb73b4fe 100644
--- a/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
+++ b/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
@@ -6,15 +6,19 @@
 
 #include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/content_client.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/public/test/test_utils.h"
@@ -330,7 +334,8 @@
   // static
   // Adds an <input> field to a given frame by executing javascript code.
   // The input can be added as the first element or the last element of
-  // |document.body|.
+  // |document.body|. The text range defined by |selection_range| will be
+  // marked.
   static void AddInputFieldToFrame(content::RenderFrameHost* rfh,
                                    const std::string& type,
                                    const std::string& value,
@@ -948,3 +953,180 @@
   EXPECT_EQ("", result);
 }
 #endif
+
+// Ideally, the following code + test should be live in
+// 'site_per_process_mac_browsertest.mm'. However, the test
+// 'LookUpStringForRangeRoutesToFocusedWidget' relies on an override in
+// ContentBrowserClient to register its filters in time. In content shell, we
+// cannot have two instances of ShellContentBrowserClient (due to a DCHECK in
+// the ctor). Therefore, we put the test here to use ChromeContentBrowserClient
+// which does not have the same singleton constraint.
+#if defined(OS_MACOSX)
+// An observer of number of open windows.
+class WindowCountObserver {
+ public:
+  explicit WindowCountObserver(size_t lower_limit) : limit_(lower_limit) {}
+  ~WindowCountObserver() {}
+
+  // Keep polling the count of open windows until the number exceeds |limit_|.
+  void WaitForLimitOrMore() {
+    size_t current_count = content::GetOpenNSWindowsCount();
+    if (current_count >= limit_)
+      return;
+
+    message_loop_runner_ = new content::MessageLoopRunner();
+    message_loop_runner_->Run();
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(&WindowCountObserver::CheckWindowCount,
+                   base::Unretained(this)));
+  }
+
+ private:
+  void CheckWindowCount() {
+    size_t current_count = content::GetOpenNSWindowsCount();
+    if (current_count >= limit_) {
+      message_loop_runner_->Quit();
+      return;
+    }
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::Bind(&WindowCountObserver::CheckWindowCount,
+                   base::Unretained(this)));
+  }
+
+  size_t limit_;
+  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowCountObserver);
+};
+
+// The original TextInputClientMessageFilter is added during the initialization
+// phase of RenderProcessHost. The only chance we have to add the test filter
+// (so that it can receive the TextInputClientMac incoming IPC messages) is
+// during the call to RenderProcessWillLaunch() on ContentBrowserClient. This
+// class provides that for testing.
+class TestBrowserClient : public ChromeContentBrowserClient {
+ public:
+  TestBrowserClient() {}
+  ~TestBrowserClient() override {}
+
+  // ContentBrowserClient overrides.
+  void RenderProcessWillLaunch(
+      content::RenderProcessHost* process_host) override {
+    ChromeContentBrowserClient::RenderProcessWillLaunch(process_host);
+    filters_.push_back(
+        new content::TestTextInputClientMessageFilter(process_host));
+  }
+
+  // Retrieves the registered filter for the given RenderProcessHost. It will
+  // return false if the RenderProcessHost was initialized while a different
+  // instance of ContentBrowserClient was in action.
+  scoped_refptr<content::TestTextInputClientMessageFilter>
+  GetTextInputClientMessageFilterForProcess(
+      content::RenderProcessHost* process_host) const {
+    for (auto filter : filters_) {
+      if (filter->process() == process_host)
+        return filter;
+    }
+    return nullptr;
+  }
+
+ private:
+  std::vector<scoped_refptr<content::TestTextInputClientMessageFilter>>
+      filters_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestBrowserClient);
+};
+
+// This test verifies that requests for dictionary lookup based on selection
+// range are routed to the focused RenderWidgetHost.
+IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
+                       LookUpStringForRangeRoutesToFocusedWidget) {
+  // TestBrowserClient needs to replace the ChromeContenBrowserClient after most
+  // things are initialized but before the WebContents is created. Here we make
+  // that happen by creating a new WebContents in a new tab. But before the test
+  // exits, we must destroy the contents and replace the old
+  // ContentBrowserClient because the original WebContents and the new one have
+  // been initialized with the original ContentBrowserClient and the new
+  // TestBrowserClient, respectively.
+  TestBrowserClient browser_client;
+  content::ContentBrowserClient* old_browser_client =
+      content::SetBrowserClientForTesting(&browser_client);
+
+  content::WebContents* new_contents =
+      content::WebContents::Create(content::WebContents::CreateParams(
+          active_contents()->GetBrowserContext(), nullptr));
+  browser()->tab_strip_model()->InsertWebContentsAt(1, new_contents,
+                                                    TabStripModel::ADD_ACTIVE);
+  EXPECT_EQ(active_contents(), new_contents);
+
+  // Starting the test body.
+  CreateIframePage("a(b)");
+  std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
+                                                GetFrame(IndexVector{0})};
+  std::vector<content::RenderWidgetHostView*> views;
+  for (auto frame : frames)
+    views.push_back(frame->GetView());
+  std::vector<std::string> values{"main frame", "child frame"};
+
+  // Adding some field with text to each frame so that we can later query for
+  // dictionary lookup.
+  for (size_t i = 0; i < frames.size(); ++i)
+    AddInputFieldToFrame(frames[i], "text", values[i], true);
+
+  std::string result;
+  // Recording window count now so that our WindowCountObserver will detect the
+  // popup window.
+  size_t current_window_count = content::GetOpenNSWindowsCount();
+
+  // Ensure we have both focus and selected text inside the main frame.
+  EXPECT_TRUE(
+      ExecuteScript(frames[0], "document.querySelector('input').focus();"));
+
+  // Request for the dictionary lookup and intercept the word on its way back.
+  // The request is always on the tab's view which is a RenderWidgetHostViewMac.
+  content::AskForLookUpDictionaryForRange(views[0], gfx::Range(0, 4));
+
+  // Wait until the result comes back.
+  auto root_filter = browser_client.GetTextInputClientMessageFilterForProcess(
+      frames[0]->GetProcess());
+  EXPECT_TRUE(root_filter);
+  root_filter->WaitForStringFromRange();
+
+  EXPECT_EQ("main", root_filter->string_from_range());
+
+  // Wait for the popup to appear to make sure TextInputClientMac has consumed
+  // the reply handler for the previous request.
+  WindowCountObserver(current_window_count).WaitForLimitOrMore();
+
+  // Ensure we have both focus and selected text inside the child frame.
+  EXPECT_TRUE(
+      ExecuteScript(frames[1], "document.querySelector('input').focus();"));
+
+  // Record window count again for the popup observer.
+  current_window_count = content::GetOpenNSWindowsCount();
+
+  // Make another request.
+  content::AskForLookUpDictionaryForRange(views[0], gfx::Range(0, 5));
+
+  // Wait until the result comes back.
+  auto child_filter = browser_client.GetTextInputClientMessageFilterForProcess(
+      frames[1]->GetProcess());
+  child_filter->WaitForStringFromRange();
+
+  EXPECT_EQ("child", child_filter->string_from_range());
+
+  // Wait for the popup to appear to make sure TextInputClientMac has consumed
+  // the reply handler for the previous request.
+  WindowCountObserver(current_window_count).WaitForLimitOrMore();
+  // Test ends here. The rest is cleanup.
+
+  // Closing this WebContents while we still hold on to our TestBrowserClient.
+  EXPECT_TRUE(browser()->tab_strip_model()->CloseWebContentsAt(
+      1, TabStripModel::CLOSE_USER_GESTURE));
+
+  // For the cleanup of the original WebContents in tab index 0.
+  content::SetBrowserClientForTesting(old_browser_client);
+}
+#endif
diff --git a/chrome/browser/resources/chromeos/arc_support/background.js b/chrome/browser/resources/chromeos/arc_support/background.js
index 9b3e788..8dbfcc8 100644
--- a/chrome/browser/resources/chromeos/arc_support/background.js
+++ b/chrome/browser/resources/chromeos/arc_support/background.js
@@ -167,7 +167,7 @@
    * Called when the "Learn More" link is clicked.
    */
   onLearnMoreLinkClicked() {
-    showLearnMoreOverlay(this.learnMoreContent_);
+    showTextOverlay(this.learnMoreContent_);
   }
 };
 
@@ -366,7 +366,7 @@
     return;
   }
 
-  hideLearnMoreOverlay();
+  hideOverlay();
   var doc = appWindow.contentWindow.document;
   var pages = doc.getElementsByClassName('section');
   var sendFeedbackElement = doc.getElementById('button-send-feedback');
@@ -409,24 +409,47 @@
 }
 
 /**
- * Sets learn more content text and shows it as overlay dialog.
- * @param {string} content HTML formatted text to show.
+ * Shows overlay dialog and required content.
+ * @param {string} overlayClass Defines which content to show, 'overlay-url' for
+ *                              webview based content and 'overlay-text' for
+ *                              simple text view.
  */
-function showLearnMoreOverlay(content) {
+function showOverlay(overlayClass) {
   var doc = appWindow.contentWindow.document;
-  var learnMoreContainer = doc.getElementById('learn-more-container');
-  var learnMoreContent = doc.getElementById('learn-more-content');
-  learnMoreContent.innerHTML = content;
-  learnMoreContainer.hidden = false;
+  var overlayContainer = doc.getElementById('overlay-container');
+  overlayContainer.className = 'overlay ' + overlayClass;
+  overlayContainer.hidden = false;
 }
 
 /**
- * Hides learn more overlay dialog.
+ * Opens overlay dialog and shows formatted text content there.
+ * @param {string} content HTML formatted text to show.
  */
-function hideLearnMoreOverlay() {
+function showTextOverlay(content) {
   var doc = appWindow.contentWindow.document;
-  var learnMoreContainer = doc.getElementById('learn-more-container');
-  learnMoreContainer.hidden = true;
+  var textContent = doc.getElementById('overlay-text-content');
+  textContent.innerHTML = content;
+  showOverlay('overlay-text');
+}
+
+/**
+ * Opens overlay dialog and shows external URL there.
+ * @param {string} url Target URL to open in overlay dialog.
+ */
+function showURLOverlay(url) {
+  var doc = appWindow.contentWindow.document;
+  var overlayWebview = doc.getElementById('overlay-url');
+  overlayWebview.src = url;
+  showOverlay('overlay-url');
+}
+
+/**
+ * Hides overlay dialog.
+ */
+function hideOverlay() {
+  var doc = appWindow.contentWindow.document;
+  var overlayContainer = doc.getElementById('overlay-container');
+  overlayContainer.hidden = true;
 }
 
 /**
@@ -600,10 +623,10 @@
 
 
     // webview is not allowed to open links in the new window. Hook these events
-    // and open links in context of main page.
+    // and open links in overlay dialog.
     termsView.addEventListener('newwindow', function(event) {
       event.preventDefault();
-      chrome.browser.openTab({'url': event.targetUrl}, function() {});
+      showURLOverlay(event.targetUrl);
     });
 
     var onAgree = function() {
@@ -641,13 +664,12 @@
     doc.getElementById('button-retry').addEventListener('click', onRetry);
     doc.getElementById('button-send-feedback')
         .addEventListener('click', onSendFeedback);
-    doc.getElementById('learn-more-close').addEventListener(
-        'click', hideLearnMoreOverlay);
+    doc.getElementById('overlay-close').addEventListener('click', hideOverlay);
 
-    var overlay = doc.getElementById('learn-more-container');
+    var overlay = doc.getElementById('overlay-container');
     appWindow.contentWindow.cr.ui.overlay.setupOverlay(overlay);
     appWindow.contentWindow.cr.ui.overlay.globalInitialization();
-    overlay.addEventListener('cancelOverlay', hideLearnMoreOverlay);
+    overlay.addEventListener('cancelOverlay', hideOverlay);
 
     connectPort();
   };
diff --git a/chrome/browser/resources/chromeos/arc_support/main.css b/chrome/browser/resources/chromeos/arc_support/main.css
index a7704c9..e2226be 100644
--- a/chrome/browser/resources/chromeos/arc_support/main.css
+++ b/chrome/browser/resources/chromeos/arc_support/main.css
@@ -87,11 +87,34 @@
   width: 960px;
 }
 
+.overlay-text .page {
+  width: 480px;
+}
+
+.overlay-text webview {
+  display: none;
+}
+
+.overlay-url .page {
+  width: 640px;
+}
+
+.overlay-url span {
+  display: none;
+}
+
+.overlay-url webview {
+  display: block;
+  height: 300px;
+  margin: 24px 8px 8px 8px;
+  padding: 0;
+  width: 100%;
+}
+
 .page {
   font-family: 'Roboto';
   font-size: 13px;
   text-align: left;
-  width: 480px;
 }
 
 .scrollable {
diff --git a/chrome/browser/resources/chromeos/arc_support/main.html b/chrome/browser/resources/chromeos/arc_support/main.html
index 7291c940..bba6c8d 100644
--- a/chrome/browser/resources/chromeos/arc_support/main.html
+++ b/chrome/browser/resources/chromeos/arc_support/main.html
@@ -144,16 +144,17 @@
       </div>
     </div>
   </div>
-  <div id="learn-more-container" class="overlay" hidden>
+  <div id="overlay-container" class="overlay" hidden>
     <div class="page">
       <div class="close-button"></div>
       <div class="content-area">
-        <span id="learn-more-content"></span>
+        <span id="overlay-text-content"></span>
+        <webview id="overlay-url"></webview>
       </div>
       <div class="action-area">
         <div class="action-area-right">
           <div class="button-strip">
-            <button id="learn-more-close" i18n-content="learnMoreClose">
+            <button id="overlay-close" i18n-content="overlayClose">
             </button>
           </div>
         </div>
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/i_search_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/i_search_test.extjs
index fe4fe4b3..27ff448 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/i_search_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/i_search_test.extjs
@@ -64,7 +64,7 @@
   }
 };
 
-TEST_F('ISearchTest', 'Simple', function() {
+TEST_F('ISearchTest', 'DISABLED_Simple', function() {
   this.runWithLoadedTree(this.linksAndHeadingsDoc, function(rootNode) {
     var handler = new FakeISearchHandler(this);
     var search = new ISearch(rootNode);
diff --git a/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.html b/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.html
index 83e6a69e..96e3523 100644
--- a/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.html
+++ b/chrome/browser/resources/chromeos/quick_unlock/pin_keyboard.html
@@ -44,7 +44,7 @@
       .digit-button {
         align-items: center;
         background: none;
-        border-radius: 100px;
+        border-radius: 0;
         box-sizing: border-box;
         color: #000;
         display: flex;
@@ -52,12 +52,12 @@
         font-size: 18px;
         height: 48px;
         justify-content: center;
-        margin: 0 6px;
+        margin: 0;
         min-height: 48px;
         min-width: 48px;
         opacity: 0.87;
-        padding: 15px;
-        width: 48px;
+        padding: 15px 21px;
+        width: 60px;
       }
 
       [hidden=true] {
@@ -72,9 +72,29 @@
         margin-top: 6px;
       }
 
+      .backspace-button-container {
+        position: relative;
+      }
+
+      .backspace-button-container paper-ripple {
+        position: absolute;
+        top: 0;
+      }
+
+      paper-ripple {
+        border-radius: 100px;
+        color: #000;
+        height: 48px;
+        left: 6px;
+        width: 48px;
+      }
+
       .digit-button.backspace-button {
+        left: 0;
         opacity: var(--dark-primary-opacity);
         padding: 14px;
+        position: absolute;
+        top: 0;
       }
 
       .digit-button.backspace-button:not([has-content]) {
@@ -159,50 +179,74 @@
         </div>
         <hr>
         <div class="row keyboard">
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="1">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="1"
+                        noink>
             <inner-text>$i18n{pinKeyboard1}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="2">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="2"
+                        noink>
             <inner-text>$i18n{pinKeyboard2}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="3">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="3"
+                        noink>
             <inner-text>$i18n{pinKeyboard3}</inner-text>
+            <paper-ripple>
           </paper-button>
         </div>
         <div class="row keyboard">
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="4">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="4"
+                        noink>
             <inner-text>$i18n{pinKeyboard4}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="5">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="5"
+                        noink>
             <inner-text>$i18n{pinKeyboard5}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="6">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="6"
+                        noink>
             <inner-text>$i18n{pinKeyboard6}</inner-text>
+            <paper-ripple>
           </paper-button>
         </div>
         <div class="row keyboard">
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="7">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="7"
+                        noink>
             <inner-text>$i18n{pinKeyboard7}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="8">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="8"
+                        noink>
             <inner-text>$i18n{pinKeyboard8}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="9">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="9"
+                        noink>
             <inner-text>$i18n{pinKeyboard9}</inner-text>
+            <paper-ripple>
           </paper-button>
         </div>
         <div class="row keyboard bottom-row">
           <div class="digit-button"></div>
-          <paper-button class="digit-button" on-tap="onNumberTap_" value="0">
+          <paper-button class="digit-button" on-tap="onNumberTap_" value="0"
+                        noink>
             <inner-text>$i18n{pinKeyboard0}</inner-text>
+            <paper-ripple>
           </paper-button>
-          <paper-icon-button class="digit-button backspace-button"
-                             has-content$="[[hasInput_(value)]]"
-                             icon="pin-keyboard:backspace"
-                             on-pointerdown="onBackspacePointerDown_"
-                             on-pointerout="onBackspacePointerOut_"
-                             on-pointerup="onBackspacePointerUp_">
-          </paper-icon-button>
+          <div class="backspace-button-container">
+            <paper-icon-button class="digit-button backspace-button"
+                               has-content$="[[hasInput_(value)]]"
+                               icon="pin-keyboard:backspace"
+                               on-pointerdown="onBackspacePointerDown_"
+                               on-pointerout="onBackspacePointerOut_"
+                               on-pointerup="onBackspacePointerUp_"
+                               noink>
+            </paper-icon-button>
+            <paper-ripple>
+          </div>
         </div>
       </div>
     </div>
diff --git a/chrome/browser/resources/md_extensions/error_page.js b/chrome/browser/resources/md_extensions/error_page.js
index 18a03e7527..10fa7848 100644
--- a/chrome/browser/resources/md_extensions/error_page.js
+++ b/chrome/browser/resources/md_extensions/error_page.js
@@ -78,10 +78,13 @@
     },
 
     /**
-     * @param {!{model:Object}} e
+     * @param {!Event} event
      * @private
      */
-    onDeleteErrorTap_: function(e) {
+    onDeleteErrorTap_: function(event) {
+      // TODO(devlin): It would be cleaner if we could cast this to a
+      // PolymerDomRepeatEvent-type thing, but that doesn't exist yet.
+      var e = /** @type {!{model:Object}} */(event);
       this.delegate.deleteErrors(this.data.id, [e.model.item.id]);
     },
   });
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html b/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html
index 4b8d2b5b..b7dd3ef 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html
+++ b/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.html
@@ -1,2 +1,3 @@
 <link rel="href" src="chrome://resources/html/cr.html">
+<link rel="href" src="chrome://resources/html/load_time_data.html">
 <script src="/appearance_page/appearance_browser_proxy.js"></script>
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js b/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js
index d5df599..e9cd6ec 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js
+++ b/chrome/browser/resources/settings/appearance_page/appearance_browser_proxy.js
@@ -13,6 +13,9 @@
      */
     getThemeInfo: assertNotReached,
 
+    /** @return {boolean} Whether the current profile is supervised. */
+    isSupervised: assertNotReached,
+
 <if expr="chromeos">
     openWallpaperManager: assertNotReached,
 </if>
@@ -40,6 +43,11 @@
       });
     },
 
+    /** @override */
+    isSupervised: function() {
+      return loadTimeData.getBoolean('isSupervised');
+    },
+
 <if expr="chromeos">
     /** @override */
     openWallpaperManager: function() {
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_page.html b/chrome/browser/resources/settings/appearance_page/appearance_page.html
index 63b68c7..2427d068 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_page.html
+++ b/chrome/browser/resources/settings/appearance_page/appearance_page.html
@@ -67,7 +67,8 @@
           </template>
 </if>
 <if expr="is_linux and not chromeos">
-          <div class="secondary-action">
+          <div class="secondary-action" hidden="[[!showThemesSecondary_(
+            prefs.extensions.theme.id.value, useSystemTheme_)]]">
             <template is="dom-if" if="[[showUseClassic_(
                 prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp>
               <paper-button id="useDefault" on-tap="onUseDefaultTap_"
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_page.js b/chrome/browser/resources/settings/appearance_page/appearance_page.js
index f94507d..49f9fc1a 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_page.js
+++ b/chrome/browser/resources/settings/appearance_page/appearance_page.js
@@ -188,7 +188,19 @@
    * @private
    */
   showUseSystem_: function(themeId, useSystemTheme) {
-    return !!themeId || !useSystemTheme;
+    return (!!themeId || !useSystemTheme) && !this.browserProxy_.isSupervised();
+  },
+
+  /**
+   * @param {string} themeId
+   * @param {boolean} useSystemTheme
+   * @return {boolean} Whether to show the secondary area where "USE CLASSIC"
+   *     and "USE GTK+" buttons live.
+   * @private
+   */
+  showThemesSecondary_: function(themeId, useSystemTheme) {
+    return this.showUseClassic_(themeId, useSystemTheme) ||
+           this.showUseSystem_(themeId, useSystemTheme);
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/appearance_page/compiled_resources2.gyp b/chrome/browser/resources/settings/appearance_page/compiled_resources2.gyp
index 2f218e8..21443a8 100644
--- a/chrome/browser/resources/settings/appearance_page/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/appearance_page/compiled_resources2.gyp
@@ -21,6 +21,7 @@
       'dependencies': [
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert',
         '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
+        '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:load_time_data',
         '<(EXTERNS_GYP):management',
         '<(EXTERNS_GYP):chrome_send',
       ],
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
index d4d3f6a4..aea7ed44 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.html
@@ -1,20 +1,16 @@
-<link rel="import" href="chrome://resources/cr_elements/cr_shared_menu/cr_shared_menu.html">
 <link rel="import" href="chrome://resources/html/action_link.html">
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
 <link rel="import" href="/passwords_and_forms_page/address_edit_dialog.html">
 <link rel="import" href="/passwords_and_forms_page/credit_card_edit_dialog.html">
 <link rel="import" href="/passwords_and_forms_page/passwords_shared_css.html">
+<link rel="import" href="/settings_action_menu.html">
 <link rel="import" href="/settings_shared_css.html">
 
 <dom-module id="settings-autofill-section">
   <link rel="import" type="css" href="chrome://resources/css/action_link.css">
   <template>
     <style include="settings-shared passwords-shared">
-      .menu-item {
-        @apply(--settings-actionable);
-      }
-
       .type-column {
         align-items: center;
         flex: 2;
@@ -63,15 +59,16 @@
         <a is="action-link" on-tap="onAddAddressTap_">$i18n{addAddress}</a>
       </div>
     </div>
-    <cr-shared-menu id="addressSharedMenu">
-      <button id="menuEditAddress" class="list-item menu-item"
+    <dialog is="settings-action-menu" id="addressSharedMenu">
+      <button id="menuEditAddress" class="dropdown-item"
           on-tap="onMenuEditAddressTap_">$i18n{editAddress}</button>
-      <button id="menuRemoveAddress" class="list-item menu-item"
+      <button id="menuRemoveAddress" class="dropdown-item"
+          hidden$="[[!activeAddress.metadata.isLocal]]"
           on-tap="onMenuRemoveAddressTap_">$i18n{removeAddress}</button>
-    </cr-shared-menu>
-    <template is="dom-if" if="[[activeAddress]]" restamp>
+    </dialog>
+    <template is="dom-if" if="[[showAddressDialog_]]" restamp>
       <settings-address-edit-dialog address="[[activeAddress]]"
-          on-close="unstampAddressEditDialog_">
+          on-close="onAddressDialogClosed_">
       </settings-address-edit-dialog>
     </template>
     <div class="settings-box first">
@@ -118,17 +115,21 @@
         </a>
       </div>
     </div>
-    <cr-shared-menu id="creditCardSharedMenu">
-      <button id="menuEditCreditCard" class="list-item menu-item"
+    <dialog is="settings-action-menu" id="creditCardSharedMenu">
+      <button id="menuEditCreditCard" class="dropdown-item"
           on-tap="onMenuEditCreditCardTap_">$i18n{editCreditCard}</button>
-      <button id="menuRemoveCreditCard" class="list-item menu-item"
+      <button id="menuRemoveCreditCard" class="dropdown-item"
+          hidden$="[[!activeCreditCard.metadata.isLocal]]"
           on-tap="onMenuRemoveCreditCardTap_">$i18n{removeCreditCard}</button>
-      <button id="menuClearCreditCard" class="list-item menu-item"
-          on-tap="onMenuClearCreditCardTap_">$i18n{clearCreditCard}</button>
-    </cr-shared-menu>
-    <template is="dom-if" if="[[activeCreditCard]]" restamp>
+      <button id="menuClearCreditCard" class="dropdown-item"
+          on-tap="onMenuClearCreditCardTap_"
+          hidden$="[[!activeCreditCard.metadata.isCached]]">
+        $i18n{clearCreditCard}
+      </button>
+    </dialog>
+    <template is="dom-if" if="[[showCreditCardDialog_]]" restamp>
       <settings-credit-card-edit-dialog credit-card="[[activeCreditCard]]"
-          on-close="unstampCreditCardEditDialog_">
+          on-close="onCreditCardDialogClosed_">
       </settings-credit-card-edit-dialog>
     </template>
   </template>
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
index 9fa0edf..e92067ed 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/autofill_section.js
@@ -22,11 +22,14 @@
       addresses: Array,
 
       /**
-       * Assigning a non-null value triggers the add/edit dialog.
+       * The model for any address related action menus or dialogs.
        * @private {?chrome.autofillPrivate.AddressEntry}
        */
       activeAddress: Object,
 
+      /** @private */
+      showAddressDialog_: Boolean,
+
       /**
        * An array of saved addresses.
        * @type {!Array<!chrome.autofillPrivate.CreditCardEntry>}
@@ -34,15 +37,13 @@
       creditCards: Array,
 
       /**
-       * Assigning a non-null value triggers the add/edit dialog.
+       * The model for any credit card related action menus or dialogs.
        * @private {?chrome.autofillPrivate.CreditCardEntry}
        */
       activeCreditCard: Object,
-   },
 
-    listeners: {
-      'addressList.scroll': 'closeMenu_',
-      'creditCardList.scroll': 'closeMenu_',
+      /** @private */
+      showCreditCardDialog_: Boolean,
     },
 
     /**
@@ -64,30 +65,32 @@
     },
 
     /**
-     * Toggles the address overflow menu.
+     * Open the address action menu.
      * @param {!Event} e The polymer event.
      * @private
      */
     onAddressMenuTap_: function(e) {
-      // Close the other menu.
-      this.$.creditCardSharedMenu.closeMenu();
-
       var menuEvent = /** @type {!{model: !{item: !Object}}} */(e);
-      var address = /** @type {!chrome.autofillPrivate.AddressEntry} */(
+      this.activeAddress = /** @type {!chrome.autofillPrivate.AddressEntry} */(
           menuEvent.model.item);
-      this.$.menuRemoveAddress.hidden = !address.metadata.isLocal;
-      this.$.addressSharedMenu.toggleMenu(Polymer.dom(e).localTarget, address);
-      e.stopPropagation();  // Prevent the tap event from closing the menu.
+
+      var dotsButton = /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget);
+      /** @type {!SettingsActionMenuElement} */ (
+          this.$.addressSharedMenu).showAt(dotsButton);
     },
 
     /**
      * Handles tapping on the "Add address" button.
-     * @param {!Event} e
      * @private
      */
-    onAddAddressTap_: function(e) {
-      e.preventDefault();
+    onAddAddressTap_: function() {
       this.activeAddress = {};
+      this.showAddressDialog_ = true;
+    },
+
+    /** @private */
+    onAddressDialogClosed_: function() {
+      this.showAddressDialog_ = false;
     },
 
     /**
@@ -95,21 +98,12 @@
      * @private
      */
     onMenuEditAddressTap_: function() {
-      var menu = this.$.addressSharedMenu;
-      /** @type {chrome.autofillPrivate.AddressEntry} */
-      var address = menu.itemData;
-
-      if (address.metadata.isLocal)
-        this.activeAddress = address;
+      if (this.activeAddress.metadata.isLocal)
+        this.showAddressDialog_ = true;
       else
         window.open(this.i18n('manageAddressesUrl'));
 
-      menu.closeMenu();
-    },
-
-    /** @private */
-    unstampAddressEditDialog_: function(e) {
-      this.activeAddress = null;
+      this.$.addressSharedMenu.close();
     },
 
     /**
@@ -117,28 +111,24 @@
      * @private
      */
     onMenuRemoveAddressTap_: function() {
-      var menu = this.$.addressSharedMenu;
-      this.fire('remove-address', menu.itemData);
-      menu.closeMenu();
+      this.fire('remove-address', this.activeAddress);
+      this.$.addressSharedMenu.close();
     },
 
     /**
-     * Toggles the credit card overflow menu.
+     * Opens the credit card action menu.
      * @param {!Event} e The polymer event.
      * @private
      */
     onCreditCardMenuTap_: function(e) {
-      // Close the other menu.
-      this.$.addressSharedMenu.closeMenu();
-
       var menuEvent = /** @type {!{model: !{item: !Object}}} */(e);
-      var creditCard = /** @type {!chrome.autofillPrivate.CreditCardEntry} */(
-          menuEvent.model.item);
-      this.$.menuRemoveCreditCard.hidden = !creditCard.metadata.isLocal;
-      this.$.menuClearCreditCard.hidden = !creditCard.metadata.isCached;
-      this.$.creditCardSharedMenu.toggleMenu(
-          Polymer.dom(e).localTarget, creditCard);
-      e.stopPropagation();  // Prevent the tap event from closing the menu.
+      this.activeCreditCard =
+          /** @type {!chrome.autofillPrivate.CreditCardEntry} */(
+              menuEvent.model.item);
+
+      var dotsButton = /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget);
+      /** @type {!SettingsActionMenuElement} */ (
+          this.$.creditCardSharedMenu).showAt(dotsButton);
     },
 
     /**
@@ -153,7 +143,12 @@
         expirationMonth: expirationMonth.toString(),
         expirationYear: date.getFullYear().toString(),
       };
-      e.preventDefault();
+      this.showCreditCardDialog_ = true;
+    },
+
+    /** @private */
+    onCreditCardDialogClosed_: function() {
+      this.showCreditCardDialog_ = false;
     },
 
     /**
@@ -161,31 +156,22 @@
      * @private
      */
     onMenuEditCreditCardTap_: function() {
-      var menu = this.$.creditCardSharedMenu;
-      /** @type {chrome.autofillPrivate.CreditCardEntry} */
-      var creditCard = menu.itemData;
-
-      if (creditCard.metadata.isLocal)
-        this.activeCreditCard = creditCard;
+      if (this.activeCreditCard.metadata.isLocal)
+        this.showCreditCardDialog_ = true;
       else
         window.open(this.i18n('manageCreditCardsUrl'));
 
-      menu.closeMenu();
+      this.$.creditCardSharedMenu.close();
     },
 
-    /** @private */
-    unstampCreditCardEditDialog_: function(e) {
-      this.activeCreditCard = null;
-    },
 
     /**
      * Handles tapping on the "Remove" credit card button.
      * @private
      */
     onMenuRemoveCreditCardTap_: function() {
-      var menu = this.$.creditCardSharedMenu;
-      this.fire('remove-credit-card', menu.itemData);
-      menu.closeMenu();
+      this.fire('remove-credit-card', this.activeCreditCard);
+      this.$.creditCardSharedMenu.close();
     },
 
     /**
@@ -193,18 +179,8 @@
      * @private
      */
     onMenuClearCreditCardTap_: function() {
-      var menu = this.$.creditCardSharedMenu;
-      this.fire('clear-credit-card', menu.itemData);
-      menu.closeMenu();
-    },
-
-    /**
-     * Closes the overflow menus.
-     * @private
-     */
-    closeMenu_: function() {
-      this.$.addressSharedMenu.closeMenu();
-      this.$.creditCardSharedMenu.closeMenu();
+      this.fire('clear-credit-card', this.activeCreditCard);
+      this.$.creditCardSharedMenu.close();
     },
 
     /**
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/compiled_resources2.gyp b/chrome/browser/resources/settings/passwords_and_forms_page/compiled_resources2.gyp
index 26eeb69..287be01b 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/compiled_resources2.gyp
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/compiled_resources2.gyp
@@ -21,7 +21,7 @@
     {
       'target_name': 'autofill_section',
       'dependencies': [
-        '<(DEPTH)/ui/webui/resources/cr_elements/cr_shared_menu/compiled_resources2.gyp:cr_shared_menu',
+        '../compiled_resources2.gyp:settings_action_menu',
         '<(EXTERNS_GYP):autofill_private',
         'address_edit_dialog',
         'credit_card_edit_dialog',
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index 0582e64e..822fca2 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -144,7 +144,7 @@
             class="settings-box two-line" actionable
             on-tap="onSiteSettingsTap_">
           <div class="start">
-            <div>$i18n{siteSettings}</div>
+            <div>[[siteSettingsPageTitle_()]]</div>
             <div class="secondary">$i18n{siteSettingsDescription}</div>
           </div>
           <button class="subpage-arrow" is="paper-icon-button-light"></button>
@@ -172,7 +172,7 @@
         <settings-subpage
             associated-control="[[$$('#site-settings-subpage-trigger')]]"
             id="site-settings"
-            page-title="$i18n{siteSettings}">
+            page-title="[[siteSettingsPageTitle_()]]">
           <settings-site-settings-page category-selected="{{categorySelected}}">
           </settings-site-settings-page>
         </settings-subpage>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chrome/browser/resources/settings/privacy_page/privacy_page.js
index f02f8e1..50b48d9c 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.js
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.js
@@ -125,4 +125,15 @@
         this.$.spellingServiceToggleButton.checked);
   },
 </if>
+
+  /**
+   * The sub-page title for the site or content settings.
+   * @return {string}
+   * @private
+   */
+  siteSettingsPageTitle_: function() {
+    return loadTimeData.getBoolean('enableSiteSettings') ?
+        loadTimeData.getString('siteSettings') :
+        loadTimeData.getString('contentSettings');
+  },
 });
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 91de6571..03a4613e 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -227,8 +227,14 @@
       assert(routeObservers_.delete(this));
     },
 
-    /** @abstract */
-    currentRouteChanged: assertNotReached,
+    /**
+     * @param {!settings.Route|undefined} opt_newRoute
+     * @param {!settings.Route|undefined} opt_oldRoute
+     * @abstract
+     */
+    currentRouteChanged: function(opt_newRoute, opt_oldRoute) {
+      assertNotReached();
+    },
   };
 
   /**
diff --git a/chrome/browser/resources/settings/site_settings/site_list.html b/chrome/browser/resources/settings/site_settings/site_list.html
index 6d6a825..f7e3a533 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.html
+++ b/chrome/browser/resources/settings/site_settings/site_list.html
@@ -29,9 +29,7 @@
     <paper-submenu id="category" hidden
         on-paper-submenu-open="onToggle_" on-paper-submenu-close="onToggle_">
       <div class="menu-trigger settings-box" hidden$="[[allSites]]" actionable>
-        <div class="flex" id="header">
-          [[computeSiteListHeader_(sites, categoryEnabled)]]
-        </div>
+        <div class="flex" id="header">[[categoryHeader]]</div>
         <iron-icon id="icon" icon="cr:expand-more"></iron-icon>
       </div>
 
diff --git a/chrome/browser/resources/settings/site_settings/site_list.js b/chrome/browser/resources/settings/site_settings/site_list.js
index 41f844e..8c6af1e 100644
--- a/chrome/browser/resources/settings/site_settings/site_list.js
+++ b/chrome/browser/resources/settings/site_settings/site_list.js
@@ -474,29 +474,6 @@
   },
 
   /**
-   * Returns the appropriate header value for display.
-   * @param {Array<string>} siteList The list of all sites to display for this
-   *     category subtype.
-   * @param {boolean} toggleState The state of the global toggle for this
-   *     category.
-   * @private
-   */
-  computeSiteListHeader_: function(siteList, toggleState) {
-    var title = '';
-    if (this.categorySubtype == settings.PermissionValues.ALLOW) {
-      title = loadTimeData.getString(
-          toggleState ? 'siteSettingsAllow' : 'siteSettingsExceptions');
-    } else if (this.categorySubtype == settings.PermissionValues.BLOCK) {
-      title = loadTimeData.getString('siteSettingsBlock');
-    } else if (this.categorySubtype == settings.PermissionValues.SESSION_ONLY) {
-      title = loadTimeData.getString('siteSettingsSessionOnly');
-    } else {
-      return title;
-    }
-    return loadTimeData.getStringF('titleAndCount', title, siteList.length);
-  },
-
-  /**
    * Returns the appropriate site description to display. This can, for example,
    * be blank, an 'embedded on <site>' or 'Current incognito session' (or a
    * mix of the last two).
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_category.html b/chrome/browser/resources/settings/site_settings/site_settings_category.html
index d60c271..3bd99ed6 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_category.html
+++ b/chrome/browser/resources/settings/site_settings/site_settings_category.html
@@ -85,18 +85,21 @@
         category="[[category]]"
         category-subtype="[[PermissionValues.BLOCK]]"
         category-enabled="[[categoryEnabled]]"
-        selected-site="{{selectedSite}}">
-    </site-list>
-    <site-list
-        category="[[category]]"
-        category-subtype="[[PermissionValues.ALLOW]]"
-        category-enabled="[[categoryEnabled]]"
+        category-header="$i18n{siteSettingsBlock}"
         selected-site="{{selectedSite}}">
     </site-list>
     <site-list
         category="[[category]]"
         category-subtype="[[PermissionValues.SESSION_ONLY]]"
         category-enabled="[[categoryEnabled]]"
+        category-header="$i18n{siteSettingsSessionOnly}"
+        selected-site="{{selectedSite}}">
+    </site-list>
+    <site-list
+        category="[[category]]"
+        category-subtype="[[PermissionValues.ALLOW]]"
+        category-enabled="[[categoryEnabled]]"
+        category-header="$i18n{siteSettingsAllow}"
         selected-site="{{selectedSite}}">
     </site-list>
 
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h
index ee266a5..0bc1b7e 100644
--- a/chrome/browser/ui/browser_dialogs.h
+++ b/chrome/browser/ui/browser_dialogs.h
@@ -174,12 +174,19 @@
 
 #if defined(OS_CHROMEOS)
 
+// This callback informs the package name of the app selected by the user, along
+// with the reason why the Bubble was closed. The string param must have a valid
+// package name, except when the CloseReason is ERROR or DIALOG_DEACTIVATED, for
+// these cases we return a dummy value which won't be used at all and has no
+// significance.
+using IntentPickerResponse =
+    base::Callback<void(std::string, arc::ArcNavigationThrottle::CloseReason)>;
+
 // Return a pointer to the IntentPickerBubbleView::ShowBubble method.
-using BubbleShowPtr = void (*)(
-    content::WebContents*,
-    const std::vector<std::pair<std::basic_string<char>, gfx::Image>>&,
-    const base::Callback<void(size_t,
-                              arc::ArcNavigationThrottle::CloseReason)>&);
+using BubbleShowPtr =
+    void (*)(content::WebContents*,
+             const std::vector<arc::ArcNavigationThrottle::AppInfo>&,
+             const IntentPickerResponse&);
 
 BubbleShowPtr ShowIntentPickerBubble();
 
diff --git a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm
index 6f036c27..e9ee2d4e 100644
--- a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm
+++ b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm
@@ -688,6 +688,7 @@
       GetToolbarModel()->GetSecurityLevel(false);
   bool has_verbose_for_security =
       security == security_state::SecurityStateModel::DANGEROUS ||
+      security == security_state::SecurityStateModel::HTTP_SHOW_WARNING ||
       (IsSecureConnection(security) && should_show_secure_verbose_);
 
   return has_verbose_for_security && !omnibox_view_->IsEditingOrEmpty() &&
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.cc b/chrome/browser/ui/views/intent_picker_bubble_view.cc
index f805497..18f8127a 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.cc
@@ -15,7 +15,9 @@
 #include "content/public/browser/navigation_handle.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/gfx/canvas.h"
+#include "ui/views/animation/ink_drop_host_view.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/scroll_view.h"
@@ -29,46 +31,81 @@
 // Using |kMaxAppResults| as a measure of how many apps we want to show.
 constexpr size_t kMaxAppResults = arc::ArcNavigationThrottle::kMaxAppResults;
 // Main components sizes
+constexpr int kDialogDelegateInsets = 16;
 constexpr int kRowHeight = 40;
 constexpr int kMaxWidth = 320;
-constexpr int kHeaderHeight = 60;
-constexpr int kFooterHeight = 68;
-// Inter components padding
-constexpr int kButtonSeparation = 8;
-constexpr int kLabelImageSeparation = 12;
 
 // UI position wrt the Top Container
 constexpr int kTopContainerMerge = 3;
-constexpr size_t kAppTagNoneSelected = static_cast<size_t>(-1);
 
-// Arbitrary negative values to use as tags on the |always_button_| and
-// |just_once_button_|. These are negative to differentiate from the app's tags
-// which are always >= 0.
-enum class Option : int { ALWAYS = -2, JUST_ONCE };
+constexpr char kInvalidPackageName[] = "";
 
 }  // namespace
 
+// IntentPickerLabelButton
+
+// A button that represents a candidate intent handler.
+class IntentPickerLabelButton : public views::LabelButton {
+ public:
+  IntentPickerLabelButton(views::ButtonListener* listener,
+                          gfx::Image* icon,
+                          const std::string& package_name,
+                          const std::string& activity_name)
+      : LabelButton(listener,
+                    base::UTF8ToUTF16(base::StringPiece(activity_name))),
+        package_name_(package_name) {
+    SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    SetFocusBehavior(View::FocusBehavior::ALWAYS);
+    SetMinSize(gfx::Size(kMaxWidth, kRowHeight));
+    SetInkDropMode(InkDropMode::ON);
+    if (!icon->IsEmpty())
+      SetImage(views::ImageButton::STATE_NORMAL, *icon->ToImageSkia());
+    SetBorder(views::Border::CreateEmptyBorder(10, 16, 10, 0));
+  }
+
+  SkColor GetInkDropBaseColor() const override { return SK_ColorBLACK; }
+
+  void MarkAsUnselected(const ui::Event* event) {
+    AnimateInkDrop(views::InkDropState::DEACTIVATED,
+                   ui::LocatedEvent::FromIfValid(event));
+  }
+
+  void MarkAsSelected(const ui::Event* event) {
+    AnimateInkDrop(views::InkDropState::ACTIVATED,
+                   ui::LocatedEvent::FromIfValid(event));
+  }
+
+  views::InkDropState GetTargetInkDropState() {
+    return ink_drop()->GetTargetInkDropState();
+  }
+
+ private:
+  std::string package_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(IntentPickerLabelButton);
+};
+
 // static
 void IntentPickerBubbleView::ShowBubble(
     content::WebContents* web_contents,
-    const std::vector<NameAndIcon>& app_info,
-    const ThrottleCallback& throttle_cb) {
+    const std::vector<AppInfo>& app_info,
+    const IntentPickerResponse& intent_picker_cb) {
   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
-  if (!browser) {
-    throttle_cb.Run(kAppTagNoneSelected,
-                    arc::ArcNavigationThrottle::CloseReason::ERROR);
+  if (!browser || !BrowserView::GetBrowserViewForBrowser(browser)) {
+    intent_picker_cb.Run(kInvalidPackageName,
+                         arc::ArcNavigationThrottle::CloseReason::ERROR);
     return;
   }
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-  if (!browser_view) {
-    throttle_cb.Run(kAppTagNoneSelected,
-                    arc::ArcNavigationThrottle::CloseReason::ERROR);
-    return;
-  }
-
   IntentPickerBubbleView* delegate =
-      new IntentPickerBubbleView(app_info, throttle_cb, web_contents);
-  delegate->set_margins(gfx::Insets());
+      new IntentPickerBubbleView(app_info, intent_picker_cb, web_contents);
+  // TODO(djacobo): Remove the left and right insets when
+  // http://crbug.com/656662 gets fixed.
+  // Add a 1-pixel extra boundary left and right when using secondary UI.
+  if (ui::MaterialDesignController::IsSecondaryUiMaterial())
+    delegate->set_margins(gfx::Insets(16, 1, 0, 1));
+  else
+    delegate->set_margins(gfx::Insets(16, 0, 0, 0));
   delegate->set_parent_window(browser_view->GetNativeWindow());
   views::Widget* widget =
       views::BubbleDialogDelegateView::CreateBubble(delegate);
@@ -85,104 +122,65 @@
                 browser_view->GetTopContainerBoundsInScreen().width(),
                 browser_view->GetTopContainerBoundsInScreen().height() -
                     kTopContainerMerge));
+  delegate->GetDialogClientView()->set_button_row_insets(
+      gfx::Insets(kDialogDelegateInsets));
+  delegate->GetDialogClientView()->Layout();
+  delegate->GetIntentPickerLabelButtonAt(0)->MarkAsSelected(nullptr);
   widget->Show();
 }
 
 // static
 std::unique_ptr<IntentPickerBubbleView>
 IntentPickerBubbleView::CreateBubbleView(
-    const std::vector<NameAndIcon>& app_info,
-    const ThrottleCallback& throttle_cb,
+    const std::vector<AppInfo>& app_info,
+    const IntentPickerResponse& intent_picker_cb,
     content::WebContents* web_contents) {
   std::unique_ptr<IntentPickerBubbleView> bubble(
-      new IntentPickerBubbleView(app_info, throttle_cb, web_contents));
+      new IntentPickerBubbleView(app_info, intent_picker_cb, web_contents));
   bubble->Init();
   return bubble;
 }
 
-void IntentPickerBubbleView::Init() {
-  SkColor button_text_color = SkColorSetRGB(0x42, 0x85, 0xf4);
+bool IntentPickerBubbleView::Accept() {
+  RunCallback(app_info_[selected_app_tag_].package_name,
+              arc::ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED);
+  return true;
+}
 
+bool IntentPickerBubbleView::Cancel() {
+  RunCallback(app_info_[selected_app_tag_].package_name,
+              arc::ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED);
+  return true;
+}
+
+bool IntentPickerBubbleView::Close() {
+  // Whenever closing the bubble without pressing |Just once| or |Always| we
+  // need to report back that the user didn't select anything.
+  RunCallback(kInvalidPackageName,
+              arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED);
+  return true;
+}
+
+void IntentPickerBubbleView::Init() {
   views::BoxLayout* general_layout =
       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
   SetLayoutManager(general_layout);
 
-  // Create a view for the upper part of the UI, managed by a GridLayout to
-  // allow precise padding.
-  View* header = new View();
-  views::GridLayout* header_layout = new views::GridLayout(header);
-  header->SetLayoutManager(header_layout);
-  views::Label* open_with = new views::Label(
-      l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH));
-  open_with->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  open_with->SetFontList(gfx::FontList("Roboto-medium, 15px"));
-
-  views::ColumnSet* column_set = header_layout->AddColumnSet(0);
-  column_set->AddPaddingColumn(0, 16);
-  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
-                        views::GridLayout::FIXED, kMaxWidth, kMaxWidth);
-  column_set->AddPaddingColumn(0, 16);
-  // Tell the GridLayout where to start, then proceed to place the views.
-  header_layout->AddPaddingRow(0.0, 21);
-  header_layout->StartRow(0, 0);
-  header_layout->AddView(open_with);
-  header_layout->AddPaddingRow(0.0, 24);
-
-  always_button_ = new views::LabelButton(
-      this, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_ALWAYS));
-  always_button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  always_button_->SetFontList(gfx::FontList("Roboto-medium, 13px"));
-  always_button_->set_tag(static_cast<int>(Option::ALWAYS));
-  always_button_->SetMinSize(gfx::Size(80, 32));
-  always_button_->SetTextColor(views::Button::STATE_DISABLED, SK_ColorGRAY);
-  always_button_->SetTextColor(views::Button::STATE_NORMAL, button_text_color);
-  always_button_->SetTextColor(views::Button::STATE_HOVERED, button_text_color);
-  always_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  always_button_->SetState(views::Button::STATE_DISABLED);
-  always_button_->SetBorder(views::Border::CreateEmptyBorder(gfx::Insets(16)));
-
-  just_once_button_ = new views::LabelButton(
-      this, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_JUST_ONCE));
-  just_once_button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  just_once_button_->SetFontList(gfx::FontList("Roboto-medium, 13px"));
-  just_once_button_->set_tag(static_cast<int>(Option::JUST_ONCE));
-  just_once_button_->SetMinSize(gfx::Size(80, 32));
-  just_once_button_->SetState(views::Button::STATE_DISABLED);
-  just_once_button_->SetTextColor(views::Button::STATE_DISABLED, SK_ColorGRAY);
-  just_once_button_->SetTextColor(views::Button::STATE_NORMAL,
-                                  button_text_color);
-  just_once_button_->SetTextColor(views::Button::STATE_HOVERED,
-                                  button_text_color);
-  just_once_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  just_once_button_->SetBorder(
-      views::Border::CreateEmptyBorder(gfx::Insets(16)));
-
-  header_layout->StartRow(0, 0);
-  AddChildView(header);
-
   // Creates a view to hold the views for each app.
   views::View* scrollable_view = new views::View();
   views::BoxLayout* scrollable_layout =
       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
   scrollable_view->SetLayoutManager(scrollable_layout);
   for (size_t i = 0; i < app_info_.size(); ++i) {
-    views::LabelButton* tmp_label = new views::LabelButton(
-        this, base::UTF8ToUTF16(base::StringPiece(app_info_[i].first)));
-    tmp_label->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-    tmp_label->SetFontList(gfx::FontList("Roboto-regular, 13px"));
-    tmp_label->SetImageLabelSpacing(kLabelImageSeparation);
-    tmp_label->SetMinSize(gfx::Size(kMaxWidth, kRowHeight));
-    tmp_label->SetMaxSize(gfx::Size(kMaxWidth, kRowHeight));
-    tmp_label->set_tag(i);
-    if (!app_info_[i].second.IsEmpty()) {
-      tmp_label->SetImage(views::ImageButton::STATE_NORMAL,
-                          *app_info_[i].second.ToImageSkia());
-    }
-    tmp_label->SetBorder(views::Border::CreateEmptyBorder(10, 16, 10, 0));
-    scrollable_view->AddChildViewAt(tmp_label, i);
+    IntentPickerLabelButton* app_button = new IntentPickerLabelButton(
+        this, &app_info_[i].icon, app_info_[i].package_name,
+        app_info_[i].activity_name);
+    app_button->set_tag(i);
+    scrollable_view->AddChildViewAt(app_button, i);
   }
 
   scroll_view_ = new views::ScrollView();
+  scroll_view_->EnableViewPortLayer();
   scroll_view_->SetContents(scrollable_view);
   // Setting a customized ScrollBar which is shown only when the mouse pointer
   // is inside the ScrollView.
@@ -197,34 +195,28 @@
     scroll_view_->ClipHeightTo(kRowHeight, (kMaxAppResults + 0.5) * kRowHeight);
   }
   AddChildView(scroll_view_);
+}
 
-  // The lower part of the Picker contains only the 2 buttons
-  // |just_once_button_| and |always_button_|.
-  View* footer = new View();
-  footer->SetBorder(views::Border::CreateEmptyBorder(24, 0, 12, 12));
-  views::BoxLayout* buttons_layout = new views::BoxLayout(
-      views::BoxLayout::kHorizontal, 0, 0, kButtonSeparation);
-  footer->SetLayoutManager(buttons_layout);
+base::string16 IntentPickerBubbleView::GetWindowTitle() const {
+  return l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH);
+}
 
-  buttons_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
-  footer->AddChildView(just_once_button_);
-  footer->AddChildView(always_button_);
-  AddChildView(footer);
+base::string16 IntentPickerBubbleView::GetDialogButtonLabel(
+    ui::DialogButton button) const {
+  return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK
+                                       ? IDS_INTENT_PICKER_BUBBLE_VIEW_JUST_ONCE
+                                       : IDS_INTENT_PICKER_BUBBLE_VIEW_ALWAYS);
 }
 
 IntentPickerBubbleView::IntentPickerBubbleView(
-    const std::vector<NameAndIcon>& app_info,
-    ThrottleCallback throttle_cb,
+    const std::vector<AppInfo>& app_info,
+    IntentPickerResponse intent_picker_cb,
     content::WebContents* web_contents)
     : views::BubbleDialogDelegateView(nullptr /* anchor_view */,
                                       views::BubbleBorder::TOP_CENTER),
       WebContentsObserver(web_contents),
-      was_callback_run_(false),
-      throttle_cb_(throttle_cb),
-      selected_app_tag_(kAppTagNoneSelected),
-      always_button_(nullptr),
-      just_once_button_(nullptr),
+      intent_picker_cb_(intent_picker_cb),
+      selected_app_tag_(0),
       scroll_view_(nullptr),
       app_info_(app_info) {}
 
@@ -235,47 +227,18 @@
 // If the widget gets closed without an app being selected we still need to use
 // the callback so the caller can Resume the navigation.
 void IntentPickerBubbleView::OnWidgetDestroying(views::Widget* widget) {
-  if (!was_callback_run_) {
-    was_callback_run_ = true;
-    throttle_cb_.Run(
-        kAppTagNoneSelected,
-        arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED);
-  }
-}
-
-int IntentPickerBubbleView::GetDialogButtons() const {
-  return ui::DIALOG_BUTTON_NONE;
+  RunCallback(kInvalidPackageName,
+              arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED);
 }
 
 void IntentPickerBubbleView::ButtonPressed(views::Button* sender,
                                            const ui::Event& event) {
-  switch (sender->tag()) {
-    case static_cast<int>(Option::ALWAYS):
-      was_callback_run_ = true;
-      throttle_cb_.Run(selected_app_tag_,
-                       arc::ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED);
-      GetWidget()->Close();
-      break;
-    case static_cast<int>(Option::JUST_ONCE):
-      was_callback_run_ = true;
-      throttle_cb_.Run(
-          selected_app_tag_,
-          arc::ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED);
-      GetWidget()->Close();
-      break;
-    default:
-      // The selected app must be a value in the range [0, app_info_.size()-1].
-      DCHECK(static_cast<size_t>(sender->tag()) < app_info_.size());
-      // The user cannot click on the |always_button_| or |just_once_button_|
-      // unless an explicit app has been selected.
-      if (selected_app_tag_ != kAppTagNoneSelected)
-        SetLabelButtonBackgroundColor(selected_app_tag_, SK_ColorWHITE);
-      selected_app_tag_ = sender->tag();
-      SetLabelButtonBackgroundColor(selected_app_tag_,
-                                    SkColorSetRGB(0xeb, 0xeb, 0xeb));
-      always_button_->SetState(views::Button::STATE_NORMAL);
-      just_once_button_->SetState(views::Button::STATE_NORMAL);
-  }
+  // The selected app must be a value in the range [0, app_info_.size()-1].
+  DCHECK_LT(static_cast<size_t>(sender->tag()), app_info_.size());
+  GetIntentPickerLabelButtonAt(selected_app_tag_)->MarkAsUnselected(&event);
+
+  selected_app_tag_ = sender->tag();
+  GetIntentPickerLabelButtonAt(selected_app_tag_)->MarkAsSelected(&event);
 }
 
 gfx::Size IntentPickerBubbleView::GetPreferredSize() const {
@@ -289,29 +252,48 @@
   } else {
     apps_height *= kRowHeight;
   }
-  ps.set_height(apps_height + kHeaderHeight + kFooterHeight);
+  ps.set_height(apps_height + kDialogDelegateInsets);
   return ps;
 }
 
 // If the actual web_contents gets destroyed in the middle of the process we
 // should inform the caller about this error.
 void IntentPickerBubbleView::WebContentsDestroyed() {
-  if (!was_callback_run_) {
-    was_callback_run_ = true;
-    throttle_cb_.Run(kAppTagNoneSelected,
-                     arc::ArcNavigationThrottle::CloseReason::ERROR);
-  }
   GetWidget()->Close();
 }
 
-views::LabelButton* IntentPickerBubbleView::GetLabelButtonAt(size_t index) {
+IntentPickerLabelButton* IntentPickerBubbleView::GetIntentPickerLabelButtonAt(
+    size_t index) {
   views::View* temp_contents = scroll_view_->contents();
-  return static_cast<views::LabelButton*>(temp_contents->child_at(index));
+  return static_cast<IntentPickerLabelButton*>(temp_contents->child_at(index));
 }
 
-void IntentPickerBubbleView::SetLabelButtonBackgroundColor(size_t index,
-                                                           SkColor color) {
-  views::LabelButton* temp_lb = GetLabelButtonAt(index);
-  temp_lb->set_background(views::Background::CreateSolidBackground(color));
-  temp_lb->SchedulePaint();
+void IntentPickerBubbleView::RunCallback(
+    std::string package,
+    arc::ArcNavigationThrottle::CloseReason close_reason) {
+  if (!intent_picker_cb_.is_null()) {
+    // We must ensure |intent_picker_cb_| is only Run() once, this is why we
+    // have a temporary |callback| helper, so we can set the original callback
+    // to null and still report back to whoever started the UI.
+    auto callback = intent_picker_cb_;
+    intent_picker_cb_.Reset();
+    callback.Run(package, close_reason);
+  }
+}
+
+gfx::ImageSkia IntentPickerBubbleView::GetAppImageForTesting(size_t index) {
+  return GetIntentPickerLabelButtonAt(index)->GetImage(
+      views::Button::ButtonState::STATE_NORMAL);
+}
+
+views::InkDropState IntentPickerBubbleView::GetInkDropStateForTesting(
+    size_t index) {
+  return GetIntentPickerLabelButtonAt(index)->GetTargetInkDropState();
+}
+
+void IntentPickerBubbleView::PressButtonForTesting(size_t index,
+                                                   const ui::Event& event) {
+  views::Button* button =
+      static_cast<views::Button*>(GetIntentPickerLabelButtonAt(index));
+  ButtonPressed(button, event);
 }
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.h b/chrome/browser/ui/views/intent_picker_bubble_view.h
index e811528..019deb9 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view.h
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.h
@@ -6,11 +6,13 @@
 #define CHROME_BROWSER_UI_VIEWS_INTENT_PICKER_BUBBLE_VIEW_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/arc/arc_navigation_throttle.h"
+#include "chrome/browser/ui/browser_dialogs.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/gfx/image/image.h"
 #include "ui/views/bubble/bubble_dialog_delegate.h"
@@ -31,6 +33,8 @@
 class Event;
 }  // namespace ui
 
+class IntentPickerLabelButton;
+
 // A bubble that displays a list of aplications (icons and names), after the
 // list we show a pair of buttons which allow the user to remember the selection
 // or not. This class comunicates the user's selection with a callback used by
@@ -43,47 +47,49 @@
 //   |  ...                           |
 //   | Icon(N) Name(N)                |
 //   |                                |
-//   |           [JUST ONCE] [ALWAYS] |
+//   |           [Just once] [Always] |
 //   +--------------------------------+
 
 class IntentPickerBubbleView : public views::BubbleDialogDelegateView,
                                public views::ButtonListener,
                                public content::WebContentsObserver {
  public:
-  using NameAndIcon = arc::ArcNavigationThrottle::NameAndIcon;
-  // This callback informs the index of the app selected by the user, along with
-  // the reason why the Bubble was closed. The size_t param must have a value in
-  // the range [0, app_info.size()-1], except when the CloseReason is ERROR or
-  // DIALOG_DEACTIVATED, for these cases we return a dummy value
-  // |kAppTagNoneSelected| which won't be used at all and has no significance.
-  using ThrottleCallback =
-      base::Callback<void(size_t, arc::ArcNavigationThrottle::CloseReason)>;
+  using AppInfo = arc::ArcNavigationThrottle::AppInfo;
 
   ~IntentPickerBubbleView() override;
   static void ShowBubble(content::WebContents* web_contents,
-                         const std::vector<NameAndIcon>& app_info,
-                         const ThrottleCallback& throttle_cb);
+                         const std::vector<AppInfo>& app_info,
+                         const IntentPickerResponse& intent_picker_cb);
   static std::unique_ptr<IntentPickerBubbleView> CreateBubbleView(
-      const std::vector<NameAndIcon>& app_info,
-      const ThrottleCallback& throttle_cb,
+      const std::vector<AppInfo>& app_info,
+      const IntentPickerResponse& intent_picker_cb,
       content::WebContents* web_contents);
 
+  // views::BubbleDialogDelegateView overrides:
+  bool Accept() override;
+  bool Cancel() override;
+  bool Close() override;
+
  protected:
   // views::BubbleDialogDelegateView overrides:
   void Init() override;
+  base::string16 GetWindowTitle() const override;
+  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
 
  private:
   friend class IntentPickerBubbleViewTest;
   FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, NullIcons);
   FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, NonNullIcons);
   FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, LabelsPtrVectorSize);
-  IntentPickerBubbleView(const std::vector<NameAndIcon>& app_info,
-                         ThrottleCallback throttle_cb,
+  FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, VerifyStartingInkDrop);
+  FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, InkDropStateTransition);
+  FRIEND_TEST_ALL_PREFIXES(IntentPickerBubbleViewTest, PressButtonTwice);
+  IntentPickerBubbleView(const std::vector<AppInfo>& app_info,
+                         IntentPickerResponse intent_picker_cb,
                          content::WebContents* web_contents);
 
   // views::BubbleDialogDelegateView overrides:
   void OnWidgetDestroying(views::Widget* widget) override;
-  int GetDialogButtons() const override;
 
   // views::ButtonListener overrides:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
@@ -94,26 +100,25 @@
   // content::WebContentsObserver overrides:
   void WebContentsDestroyed() override;
 
-  // Retrieves the LabelButton* contained at position |index| from the internal
-  // ScrollView.
-  views::LabelButton* GetLabelButtonAt(size_t index);
-  void SetLabelButtonBackgroundColor(size_t index, SkColor color);
+  // Retrieves the IntentPickerLabelButton* contained at position |index| from
+  // the internal ScrollView.
+  IntentPickerLabelButton* GetIntentPickerLabelButtonAt(size_t index);
+  void RunCallback(std::string package,
+                   arc::ArcNavigationThrottle::CloseReason close_reason);
 
-  // Flag set to true iff the callback was Run at some previous step, used to
-  // ensure we only use the callback once.
-  bool was_callback_run_;
+  gfx::ImageSkia GetAppImageForTesting(size_t index);
+  views::InkDropState GetInkDropStateForTesting(size_t);
+  void PressButtonForTesting(size_t index, const ui::Event& event);
 
   // Callback used to respond to ArcNavigationThrottle.
-  ThrottleCallback throttle_cb_;
+  IntentPickerResponse intent_picker_cb_;
 
-  // Keeps a invalid value unless the user explicitly makes a decision.
-  size_t selected_app_tag_;
+  // Pre-select the first app on the list.
+  size_t selected_app_tag_ = 0;
 
-  views::LabelButton* always_button_;
-  views::LabelButton* just_once_button_;
   views::ScrollView* scroll_view_;
 
-  std::vector<NameAndIcon> app_info_;
+  std::vector<AppInfo> app_info_;
 
   DISALLOW_COPY_AND_ASSIGN(IntentPickerBubbleView);
 };
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
index 6a78a69..39ce248 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/views/intent_picker_bubble_view.h"
 
+#include <string>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/callback.h"
@@ -14,16 +16,21 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image.h"
 #include "ui/views/controls/button/button.h"
-#include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/resources/grit/views_resources.h"
 #include "url/gurl.h"
 
-using NameAndIcon = arc::ArcNavigationThrottle::NameAndIcon;
+using AppInfo = arc::ArcNavigationThrottle::AppInfo;
 using content::WebContents;
 using content::OpenURLParams;
 using content::Referrer;
 
+class MousePressedEvent : public ui::Event {
+ public:
+  MousePressedEvent() : Event(ui::ET_MOUSE_PRESSED, base::TimeTicks(), 0) {}
+  ~MousePressedEvent() override {}
+};
+
 class IntentPickerBubbleViewTest : public BrowserWithTestWindowTest {
  public:
   IntentPickerBubbleViewTest() = default;
@@ -38,8 +45,8 @@
  protected:
   void CreateBubbleView(bool use_icons) {
     // Pushing a couple of fake apps just to check they are created on the UI.
-    app_info_.emplace_back("dank app 1", gfx::Image());
-    app_info_.emplace_back("dank app 2", gfx::Image());
+    app_info_.emplace_back(AppInfo(gfx::Image(), "package_1", "dank app 1"));
+    app_info_.emplace_back(AppInfo(gfx::Image(), "package_2", "dank_app_2"));
 
     if (use_icons)
       FillAppListWithDummyIcons();
@@ -61,15 +68,15 @@
     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     gfx::Image dummy_icon = rb.GetImageNamed(IDR_CLOSE);
     for (auto& app : app_info_)
-      app.second = dummy_icon;
+      app.icon = dummy_icon;
   }
 
   // Dummy method to be called upon bubble closing.
-  void OnBubbleClosed(size_t selected_app_tag,
+  void OnBubbleClosed(std::string selected_app_package,
                       arc::ArcNavigationThrottle::CloseReason close_reason) {}
 
   std::unique_ptr<IntentPickerBubbleView> bubble_;
-  std::vector<NameAndIcon> app_info_;
+  std::vector<AppInfo> app_info_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(IntentPickerBubbleViewTest);
@@ -80,9 +87,8 @@
   CreateBubbleView(false);
   size_t size = bubble_->app_info_.size();
   for (size_t i = 0; i < size; ++i) {
-    views::LabelButton* app = bubble_->GetLabelButtonAt(i);
-    EXPECT_TRUE(
-        app->GetImage(views::Button::ButtonState::STATE_NORMAL).isNull()) << i;
+    gfx::ImageSkia image = bubble_->GetAppImageForTesting(i);
+    EXPECT_TRUE(image.isNull()) << i;
   }
 }
 
@@ -91,9 +97,8 @@
   CreateBubbleView(true);
   size_t size = bubble_->app_info_.size();
   for (size_t i = 0; i < size; ++i) {
-    views::LabelButton* app = bubble_->GetLabelButtonAt(i);
-    EXPECT_FALSE(
-        app->GetImage(views::Button::ButtonState::STATE_NORMAL).isNull()) << i;
+    gfx::ImageSkia image = bubble_->GetAppImageForTesting(i);
+    EXPECT_FALSE(image.isNull()) << i;
   }
 }
 
@@ -103,3 +108,40 @@
   CreateBubbleView(true);
   EXPECT_EQ(app_info_.size(), bubble_->app_info_.size());
 }
+
+// Verifies the InkDrop state when creating a new bubble.
+TEST_F(IntentPickerBubbleViewTest, VerifyStartingInkDrop) {
+  CreateBubbleView(true);
+  size_t size = bubble_->app_info_.size();
+  for (size_t i = 0; i < size; ++i) {
+    EXPECT_EQ(bubble_->GetInkDropStateForTesting(i),
+              views::InkDropState::HIDDEN);
+  }
+}
+
+// Press each button at a time and make sure it goes to ACTIVATED state,
+// followed by HIDDEN state after selecting other button.
+TEST_F(IntentPickerBubbleViewTest, InkDropStateTransition) {
+  CreateBubbleView(true);
+  size_t size = bubble_->app_info_.size();
+  for (size_t i = 0; i < size; ++i) {
+    bubble_->PressButtonForTesting((i + 1) % size, MousePressedEvent());
+    EXPECT_EQ(bubble_->GetInkDropStateForTesting(i),
+              views::InkDropState::HIDDEN);
+    EXPECT_EQ(bubble_->GetInkDropStateForTesting((i + 1) % size),
+              views::InkDropState::ACTIVATED);
+  }
+}
+
+// Arbitrary press the first button twice, check that the InkDropState remains
+// the same.
+TEST_F(IntentPickerBubbleViewTest, PressButtonTwice) {
+  CreateBubbleView(true);
+  EXPECT_EQ(bubble_->GetInkDropStateForTesting(0), views::InkDropState::HIDDEN);
+  bubble_->PressButtonForTesting(0, MousePressedEvent());
+  EXPECT_EQ(bubble_->GetInkDropStateForTesting(0),
+            views::InkDropState::ACTIVATED);
+  bubble_->PressButtonForTesting(0, MousePressedEvent());
+  EXPECT_EQ(bubble_->GetInkDropStateForTesting(0),
+            views::InkDropState::ACTIVATED);
+}
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 38c7c2e..dc70d7e 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -825,7 +825,8 @@
   security_state::SecurityStateModel::SecurityLevel security_level =
       GetToolbarModel()->GetSecurityLevel(false);
   SkColor icon_color =
-      (security_level == security_state::SecurityStateModel::NONE)
+      (security_level == security_state::SecurityStateModel::NONE ||
+       security_level == security_state::SecurityStateModel::HTTP_SHOW_WARNING)
           ? color_utils::DeriveDefaultIconColor(GetColor(TEXT))
           : GetSecureTextColor(security_level);
   location_icon_view_->SetImage(gfx::CreateVectorIcon(
@@ -1014,22 +1015,23 @@
 
 bool LocationBarView::ShouldShowSecurityChip() const {
   using SecurityLevel = security_state::SecurityStateModel::SecurityLevel;
-  SecurityLevel level = GetToolbarModel()->GetSecurityLevel(false);
-  return ((level == SecurityLevel::SECURE ||
-           level == SecurityLevel::EV_SECURE) &&
-          should_show_secure_state_) ||
-         level == SecurityLevel::DANGEROUS;
+  const SecurityLevel level = GetToolbarModel()->GetSecurityLevel(false);
+  if (level == SecurityLevel::SECURE || level == SecurityLevel::EV_SECURE)
+    return should_show_secure_state_;
+  return level == SecurityLevel::DANGEROUS ||
+         level == SecurityLevel::HTTP_SHOW_WARNING;
 }
 
 bool LocationBarView::ShouldAnimateSecurityChip() const {
   using SecurityLevel = security_state::SecurityStateModel::SecurityLevel;
   SecurityLevel level = GetToolbarModel()->GetSecurityLevel(false);
-  bool is_secure_level =
-      level == SecurityLevel::EV_SECURE || level == SecurityLevel::SECURE;
-  return ShouldShowSecurityChip() &&
-         ((level == SecurityLevel::DANGEROUS &&
-           should_animate_nonsecure_state_) ||
-          (is_secure_level && should_animate_secure_state_));
+  if (!ShouldShowSecurityChip())
+    return false;
+  if (level == SecurityLevel::SECURE || level == SecurityLevel::EV_SECURE)
+    return should_animate_secure_state_;
+  return should_animate_nonsecure_state_ &&
+         (level == SecurityLevel::DANGEROUS ||
+          level == SecurityLevel::HTTP_SHOW_WARNING);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/webui/conflicts_ui.cc b/chrome/browser/ui/webui/conflicts_ui.cc
index d39f3678..7a38864 100644
--- a/chrome/browser/ui/webui/conflicts_ui.cc
+++ b/chrome/browser/ui/webui/conflicts_ui.cc
@@ -122,7 +122,10 @@
   // easy enough to defend against this.
   if (!observer_.IsObserving(model)) {
     observer_.Add(model);
-    model->ScanNow();
+
+    // Ask the scan to be performed immediately, and not in background mode.
+    // This ensures the results are available ASAP for the UI.
+    model->ScanNow(false);
   }
 }
 
diff --git a/chrome/browser/ui/webui/settings/appearance_handler.cc b/chrome/browser/ui/webui/settings/appearance_handler.cc
index eac391fb5..e08eae0 100644
--- a/chrome/browser/ui/webui/settings/appearance_handler.cc
+++ b/chrome/browser/ui/webui/settings/appearance_handler.cc
@@ -53,7 +53,10 @@
 
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
 void AppearanceHandler::HandleUseSystemTheme(const base::ListValue* args) {
-  ThemeServiceFactory::GetForProfile(profile_)->UseSystemTheme();
+  if (profile_->IsSupervised())
+    NOTREACHED();
+  else
+    ThemeServiceFactory::GetForProfile(profile_)->UseSystemTheme();
 }
 #endif
 
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index f90b41c..722b5709 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -253,7 +253,8 @@
 }
 #endif
 
-void AddAppearanceStrings(content::WebUIDataSource* html_source) {
+void AddAppearanceStrings(content::WebUIDataSource* html_source,
+                          Profile* profile) {
   LocalizedString localized_strings[] = {
     {"appearancePageTitle", IDS_SETTINGS_APPEARANCE},
     {"exampleDotCom", IDS_SETTINGS_EXAMPLE_DOT_COM},
@@ -284,6 +285,8 @@
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
                           arraysize(localized_strings));
+
+  html_source->AddBoolean("isSupervised", profile->IsSupervised());
 }
 
 #if defined(OS_CHROMEOS)
@@ -1225,6 +1228,7 @@
       {"manageCertificates", IDS_SETTINGS_MANAGE_CERTIFICATES},
       {"manageCertificatesDescription",
        IDS_SETTINGS_MANAGE_CERTIFICATES_DESCRIPTION},
+      {"contentSettings", IDS_SETTINGS_CONTENT_SETTINGS},
       {"siteSettings", IDS_SETTINGS_SITE_SETTINGS},
       {"siteSettingsDescription", IDS_SETTINGS_SITE_SETTINGS_DESCRIPTION},
       {"clearBrowsingData", IDS_SETTINGS_CLEAR_DATA},
@@ -1694,7 +1698,7 @@
 #if defined(OS_CHROMEOS)
   AddAccountUITweaksStrings(html_source, profile);
 #endif
-  AddAppearanceStrings(html_source);
+  AddAppearanceStrings(html_source, profile);
 #if defined(OS_CHROMEOS)
   AddBluetoothStrings(html_source);
 #endif
diff --git a/chrome/browser/ui/webui/welcome_handler.cc b/chrome/browser/ui/webui/welcome_handler.cc
index f23d092a..c6b91f6 100644
--- a/chrome/browser/ui/webui/welcome_handler.cc
+++ b/chrome/browser/ui/webui/welcome_handler.cc
@@ -18,7 +18,6 @@
 
 WelcomeHandler::WelcomeHandler(content::WebUI* web_ui)
     : profile_(Profile::FromWebUI(web_ui)),
-      browser_(chrome::FindBrowserWithWebContents(web_ui->GetWebContents())),
       oauth2_token_service_(
           ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)),
       result_(WelcomeResult::DEFAULT) {
@@ -46,7 +45,7 @@
     // them away to the NTP instead.
     GoToNewTabPage();
   } else {
-    browser_->ShowModalSigninWindow(
+    GetBrowser()->ShowModalSigninWindow(
         profiles::BubbleViewMode::BUBBLE_VIEW_MODE_GAIA_SIGNIN,
         signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE);
   }
@@ -69,7 +68,16 @@
 }
 
 void WelcomeHandler::GoToNewTabPage() {
-  chrome::NavigateParams params(browser_, GURL(chrome::kChromeUINewTabURL),
+  chrome::NavigateParams params(GetBrowser(), GURL(chrome::kChromeUINewTabURL),
                                 ui::PageTransition::PAGE_TRANSITION_LINK);
   chrome::Navigate(&params);
 }
+
+Browser* WelcomeHandler::GetBrowser() {
+  DCHECK(web_ui());
+  content::WebContents* contents = web_ui()->GetWebContents();
+  DCHECK(contents);
+  Browser* browser = chrome::FindBrowserWithWebContents(contents);
+  DCHECK(browser);
+  return browser;
+}
diff --git a/chrome/browser/ui/webui/welcome_handler.h b/chrome/browser/ui/webui/welcome_handler.h
index a9a7af7a..55f1861 100644
--- a/chrome/browser/ui/webui/welcome_handler.h
+++ b/chrome/browser/ui/webui/welcome_handler.h
@@ -37,8 +37,9 @@
   void HandleUserDecline(const base::ListValue* args);
   void GoToNewTabPage();
 
+  Browser* GetBrowser();
+
   Profile* profile_;
-  Browser* browser_;
   ProfileOAuth2TokenService* oauth2_token_service_;
   WelcomeResult result_;
 
diff --git a/chrome/browser/win/enumerate_modules_model.cc b/chrome/browser/win/enumerate_modules_model.cc
index 376e6b08..7d301ca 100644
--- a/chrome/browser/win/enumerate_modules_model.cc
+++ b/chrome/browser/win/enumerate_modules_model.cc
@@ -60,6 +60,12 @@
 
 namespace {
 
+// The default amount of time between module inspections. This prevents
+// certificate inspection and validation from using a large amount of CPU and
+// battery immediately after startup.
+constexpr base::TimeDelta kDefaultPerModuleDelay =
+  base::TimeDelta::FromSeconds(1);
+
 // A struct to help de-duping modules before adding them to the enumerated
 // modules vector.
 struct FindModule {
@@ -393,7 +399,8 @@
 
 ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer)
     : enumerated_modules_(nullptr),
-      observer_(observer) {
+      observer_(observer),
+      per_module_delay_(kDefaultPerModuleDelay) {
 }
 
 ModuleEnumerator::~ModuleEnumerator() {
@@ -408,12 +415,17 @@
   // scanning has not been finished before shutdown.
   BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
       FROM_HERE,
-      base::Bind(&ModuleEnumerator::ScanImpl,
+      base::Bind(&ModuleEnumerator::ScanImplStart,
                  base::Unretained(this)),
       base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
 }
 
-void ModuleEnumerator::ScanImpl() {
+void ModuleEnumerator::SetPerModuleDelayToZero() {
+  // Set the delay to zero so the modules enumerate as quickly as possible.
+  per_module_delay_ = base::TimeDelta::FromSeconds(0);
+}
+
+void ModuleEnumerator::ScanImplStart() {
   base::TimeTicks start_time = base::TimeTicks::Now();
 
   // The provided destination for the enumerated modules should be empty, as it
@@ -444,6 +456,61 @@
   UMA_HISTOGRAM_TIMES("Conflicts.EnumerateWinsockModules",
                       checkpoint2 - checkpoint);
 
+  enumeration_total_time_ = base::TimeTicks::Now() - start_time;
+
+  // Post a delayed task to scan the first module. This forwards directly to
+  // ScanImplFinish if there are no modules to scan.
+  BrowserThread::GetBlockingPool()->PostDelayedWorkerTask(
+      FROM_HERE,
+      base::Bind(&ModuleEnumerator::ScanImplModule,
+                 base::Unretained(this),
+                 0),
+      per_module_delay_);
+}
+
+void ModuleEnumerator::ScanImplDelay(size_t index) {
+  // Bounce this over to a CONTINUE_ON_SHUTDOWN task in the same pool. This is
+  // necessary to prevent shutdown hangs while inspecting a module.
+  BrowserThread::GetBlockingPool()->PostWorkerTaskWithShutdownBehavior(
+      FROM_HERE,
+      base::Bind(&ModuleEnumerator::ScanImplModule,
+                 base::Unretained(this),
+                 index),
+      base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+}
+
+void ModuleEnumerator::ScanImplModule(size_t index) {
+  while (index < enumerated_modules_->size()) {
+    base::TimeTicks start_time = base::TimeTicks::Now();
+    Module& entry = enumerated_modules_->at(index);
+    PopulateModuleInformation(&entry);
+    CollapsePath(&entry);
+    base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+    enumeration_inspection_time_ += elapsed;
+    enumeration_total_time_ += elapsed;
+
+    // With a non-zero delay, bounce back over to ScanImplDelay, which will
+    // bounce back to this function and inspect the next module.
+    if (!per_module_delay_.is_zero()) {
+      BrowserThread::GetBlockingPool()->PostDelayedWorkerTask(
+          FROM_HERE,
+          base::Bind(&ModuleEnumerator::ScanImplDelay,
+                     base::Unretained(this),
+                     index + 1),
+          per_module_delay_);
+      return;
+    }
+
+    // If the delay has been set to zero then simply finish the rest of the
+    // enumeration in this already started task.
+    ++index;
+  }
+
+  // Getting here means that all of the modules have been inspected.
+  return ScanImplFinish();
+}
+
+void ModuleEnumerator::ScanImplFinish() {
   // TODO(chrisha): Annotate any modules that are suspicious/bad.
 
   ReportThirdPartyMetrics();
@@ -451,8 +518,10 @@
   std::sort(enumerated_modules_->begin(),
             enumerated_modules_->end(), ModuleSort);
 
+  UMA_HISTOGRAM_TIMES("Conflicts.EnumerationInspectionTime",
+                      enumeration_inspection_time_);
   UMA_HISTOGRAM_TIMES("Conflicts.EnumerationTotalTime",
-                      base::TimeTicks::Now() - start_time);
+                      enumeration_total_time_);
 
   // Send a reply back on the UI thread. The |observer_| outlives this
   // enumerator, so posting a raw pointer is safe. This is done last as
@@ -482,10 +551,7 @@
     Module entry;
     entry.type = LOADED_MODULE;
     entry.location = module.szExePath;
-    PopulateModuleInformation(&entry);
-
     NormalizeModule(&entry);
-    CollapsePath(&entry);
     enumerated_modules_->push_back(entry);
   } while (::Module32Next(snap.Get(), &module));
 }
@@ -515,10 +581,7 @@
     Module entry;
     entry.type = SHELL_EXTENSION;
     entry.location = dll;
-    PopulateModuleInformation(&entry);
-
     NormalizeModule(&entry);
-    CollapsePath(&entry);
     AddToListWithoutDuplicating(entry);
 
     ++registration;
@@ -542,12 +605,9 @@
     wchar_t expanded[MAX_PATH];
     DWORD size = ExpandEnvironmentStrings(
         entry.location.c_str(), expanded, MAX_PATH);
-    if (size != 0 && size <= MAX_PATH) {
-      GetCertificateInfo(base::FilePath(expanded), &entry.cert_info);
-    }
+    if (size != 0 && size <= MAX_PATH)
+      entry.location = expanded;
     entry.version = base::IntToString16(layered_providers[i].version);
-
-    // Paths have already been collapsed.
     NormalizeModule(&entry);
     AddToListWithoutDuplicating(entry);
   }
@@ -756,20 +816,29 @@
     BrowserThread::PostAfterStartupTask(
         FROM_HERE,
         BrowserThread::GetTaskRunnerForThread(BrowserThread::UI),
-        base::Bind(&EnumerateModulesModel::ScanNow, base::Unretained(this)));
+        base::Bind(&EnumerateModulesModel::ScanNow,
+                   base::Unretained(this),
+                   true));
     done = true;
   }
 }
 
-void EnumerateModulesModel::ScanNow() {
+void EnumerateModulesModel::ScanNow(bool background_mode) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // |module_enumerator_| is used as a lock to know whether or not there are
   // active/pending blocking pool tasks. If a module enumerator exists then a
   // scan is already underway. Otherwise, either no scan has been completed or
   // a scan has terminated.
-  if (module_enumerator_)
+  if (module_enumerator_) {
+    // If a scan is in progress and this request is for immediate results, then
+    // inform the background scan. This is done without any locks because the
+    // other thread only reads from the value that is being modified, and on
+    // Windows its an atomic write.
+    if (!background_mode)
+      module_enumerator_->SetPerModuleDelayToZero();
     return;
+  }
 
   // Only allow a single scan per process lifetime. Immediately notify any
   // observers that the scan is complete. At this point |enumerated_modules_| is
@@ -781,6 +850,8 @@
 
   // ScanNow does not block, rather it simply schedules a task.
   module_enumerator_.reset(new ModuleEnumerator(this));
+  if (!background_mode)
+    module_enumerator_->SetPerModuleDelayToZero();
   module_enumerator_->ScanNow(&enumerated_modules_);
 }
 
diff --git a/chrome/browser/win/enumerate_modules_model.h b/chrome/browser/win/enumerate_modules_model.h
index 5b3beac..045e05c 100644
--- a/chrome/browser/win/enumerate_modules_model.h
+++ b/chrome/browser/win/enumerate_modules_model.h
@@ -155,28 +155,51 @@
   // will notify when done by calling the DoneScanning method of |observer_|.
   void ScanNow(ModulesVector* list);
 
+  // Sets |per_module_delay_| to zero, causing the modules to be inspected
+  // in realtime.
+  void SetPerModuleDelayToZero();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(EnumerateModulesTest, CollapsePath);
 
-  // This function does the actual file scanning work in the blocking pool. It
-  // enumerates all loaded modules in the process and other modules of interest,
-  // such as the registered Winsock LSP modules and stores them in
-  // |enumerated_modules_|. It then normalizes the module info and matches them
-  // against a blacklist of known bad modules. Finally, notifies the observer
-  // that the enumeration is complete by invoking DoneScanning.
-  void ScanImpl();
+  // This function enumerates all modules in the blocking pool. Once the list of
+  // module filenames is populated it posts a delayed task to call
+  // ScanImplDelay for the first module.
+  void ScanImplStart();
 
-  // Enumerate all modules loaded into the Chrome process.
+  // Immediately posts a CONTINUE_ON_SHUTDOWN task to ScanImplModule for the
+  // given module. This ping-ponging is because the blocking pool does not
+  // offer a delayed CONTINUE_ON_SHUTDOWN task.
+  // TODO(chrisha): When the new scheduler enables delayed CONTINUE_ON_SHUTDOWN
+  // tasks, simplify this logic.
+  void ScanImplDelay(size_t index);
+
+  // Inspects the module in |enumerated_modules_| at the given |index|. Gets
+  // module information, normalizes it, and collapses the path. This is an
+  // expensive operation and non-critical. Posts a delayed task to ScanImplDelay
+  // for the next module. When all modules are finished forwards directly to
+  // ScanImplFinish.
+  void ScanImplModule(size_t index);
+
+  // Collects metrics and notifies the observer that the enumeration is complete
+  // by invoking DoneScanning on the UI thread.
+  void ScanImplFinish();
+
+  // Enumerate all modules loaded into the Chrome process. Creates empty
+  // entries in |enumerated_modules_| with a populated |location| field.
   void EnumerateLoadedModules();
 
-  // Enumerate all registered Windows shell extensions.
+  // Enumerate all registered Windows shell extensions. Creates empty
+  // entries in |enumerated_modules_| with a populated |location| field.
   void EnumerateShellExtensions();
 
-  // Enumerate all registered Winsock LSP modules.
+  // Enumerate all registered Winsock LSP modules. Creates empty
+  // entries in |enumerated_modules_| with a populated |location| field.
   void EnumerateWinsockModules();
 
   // Reads the registered shell extensions found under |parent| key in the
-  // registry.
+  // registry. Creates empty entries in |enumerated_modules_| with a populated
+  // |location| field.
   void ReadShellExtensions(HKEY parent);
 
   // Given a |module|, initializes the structure and loads additional
@@ -202,8 +225,7 @@
   void CollapsePath(Module* module);
 
   // Reports (via UMA) a handful of high-level metrics regarding third party
-  // modules in this process. Called by ScanImpl after modules have been
-  // enumerated and processed.
+  // modules in this process. Called by ScanImplFinish.
   void ReportThirdPartyMetrics();
 
   // The typedef for the vector that maps a regular file path to %env_var%.
@@ -220,6 +242,19 @@
   // The observer, which needs to be notified when the scan is complete.
   EnumerateModulesModel* observer_;
 
+  // The delay that is observed between module inspection tasks. This is
+  // currently 1 second, which means it takes several minutes to iterate over
+  // all modules on average.
+  base::TimeDelta per_module_delay_;
+
+  // The amount of time taken for on-disk module inspection. Reported in
+  // ScanImplFinish.
+  base::TimeDelta enumeration_inspection_time_;
+
+  // The total amount of time taken for module enumeration. Reported in
+  // ScanImplFinish.
+  base::TimeDelta enumeration_total_time_;
+
   DISALLOW_COPY_AND_ASSIGN(ModuleEnumerator);
 };
 
@@ -295,10 +330,14 @@
   int modules_to_notify_about() const;
 
   // Checks to see if a scanning task should be started and sets one off, if so.
+  // This will cause ScanNow to be invoked in background mode.
   void MaybePostScanningTask();
 
-  // Asynchronously start the scan for the loaded module list.
-  void ScanNow();
+  // Asynchronously start the scan for the loaded module list. If
+  // |background_mode| is true the scan will happen slowly over a process of
+  // minutes, spread across dozens or even hundreds of delayed tasks. Otherwise
+  // the processing will occur in a single task.
+  void ScanNow(bool background_mode);
 
   // Gets the whole module list as a ListValue.
   base::ListValue* GetModuleList();
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 14d9ffd..ae6adbb 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -108,6 +108,13 @@
 const base::Feature kMaterialDesignSettings{
     "MaterialDesignSettings", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables the use of native notification centers instead of using the Message
+// Center for displaying the toasts.
+#if defined(OS_MACOSX)
+const base::Feature kNativeNotifications{"NativeNotifications",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+#endif  // defined(OS_MACOSX)
+
 #if defined(ENABLE_PLUGINS)
 // Prefer HTML content by hiding Flash from the list of plugins.
 // https://crbug.com/626728
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 887f422..a24f65f 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -61,6 +61,10 @@
 
 extern const base::Feature kMaterialDesignSettings;
 
+#if defined(OS_MACOSX)
+extern const base::Feature kNativeNotifications;
+#endif  // defined(OS_MACOSX)
+
 #if defined(ENABLE_PLUGINS)
 extern const base::Feature kPreferHtmlOverPlugins;
 #endif
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 229a3194..b01eed8 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -391,10 +391,6 @@
 // (internally adds lEnableGpuPlugin to the command line).
 const char kEnableNaCl[]                    = "enable-nacl";
 
-// Enables the use of native notifications instead of using the Chrome based
-// ones.
-const char kEnableNativeNotifications[] = "enable-native-notifications";
-
 // Enables tracing for each navigation. It will attempt to trace each navigation
 // for 10s, until the buffer is full, or until the next navigation.
 // It only works if a URL was provided by --trace-upload-url.
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 12e9d72..2122084 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -124,7 +124,6 @@
 extern const char kEnableMaterialDesignFeedback[];
 extern const char kEnableMaterialDesignPolicyPage[];
 extern const char kEnableNaCl[];
-extern const char kEnableNativeNotifications[];
 extern const char kEnableNavigationTracing[];
 extern const char kEnableNetBenchmarking[];
 extern const char kEnableNewBookmarkApps[];
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 8f63dcd..dc35b36 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -621,6 +621,7 @@
   kChromeUIHistoryHost,
   kChromeUIInvalidationsHost,
   kChromeUILocalStateHost,
+  kChromeUINetExportHost,
   kChromeUINetInternalsHost,
   kChromeUINewTabHost,
   kChromeUIOmniboxHost,
diff --git a/chrome/installer/linux/BUILD.gn b/chrome/installer/linux/BUILD.gn
index 1394af3..c9e0e78 100644
--- a/chrome/installer/linux/BUILD.gn
+++ b/chrome/installer/linux/BUILD.gn
@@ -5,6 +5,7 @@
 import("//build/config/chrome_build.gni")
 import("//build/config/features.gni")
 import("//build/config/sanitizers/sanitizers.gni")
+import("//build/config/sysroot.gni")
 import("//build/util/process_version.gni")
 import("//build/util/version.gni")
 import("//chrome/process_version_rc_template.gni")  # For branding_file_path.
@@ -107,10 +108,10 @@
     "debian/changelog.template",
     "debian/control.template",
     "debian/debian.menu",
-    "debian/expected_deps_ia32_precise",
-    "debian/expected_deps_ia32_trusty",
-    "debian/expected_deps_x64_precise",
-    "debian/expected_deps_x64_trusty",
+    "debian/expected_deps_ia32_jessie",
+    "debian/expected_deps_ia32_wheezy",
+    "debian/expected_deps_x64_jessie",
+    "debian/expected_deps_x64_wheezy",
     "debian/postinst",
     "debian/postrm",
     "debian/prerm",
@@ -283,6 +284,10 @@
       invoker.channel,
       "-d",
       branding_path_component,
+      "-s",
+      rebase_path(sysroot),
+      "-e",
+      rebase_path("//third_party/dpkg-dev"),
     ]
     deps = [
       ":installer_deps",
diff --git a/chrome/installer/linux/debian/build.sh b/chrome/installer/linux/debian/build.sh
index e43c4377..d8c50a67 100755
--- a/chrome/installer/linux/debian/build.sh
+++ b/chrome/installer/linux/debian/build.sh
@@ -128,6 +128,8 @@
   echo "-o dir     package output directory [${OUTPUTDIR}]"
   echo "-b dir     build input directory    [${BUILDDIR}]"
   echo "-d brand   either chromium or google_chrome"
+  echo "-s dir     /path/to/sysroot"
+  echo "-e dir     /path/to/dpkg-dev"
   echo "-h         this help message"
 }
 
@@ -162,7 +164,7 @@
 }
 
 process_opts() {
-  while getopts ":o:b:c:a:d:h" OPTNAME
+  while getopts ":e:s:o:b:c:a:d:h" OPTNAME
   do
     case $OPTNAME in
       o )
@@ -181,6 +183,12 @@
       d )
         BRANDING="$OPTARG"
         ;;
+      s )
+        SYSROOT="$OPTARG"
+        ;;
+      e )
+        DPKG_DEV_DIR="$OPTARG"
+        ;;
       h )
         usage
         exit 0
@@ -217,20 +225,21 @@
 else
   TARGETARCH="ia32"
 fi
-if [[ "$(lsb_release -c)" = *"precise" ]]; then
-  HOST_DISTRO="precise"
-elif [[ "$(lsb_release -c)" = *"trusty" ]]; then
-  HOST_DISTRO="trusty"
-else
-  echo "Debian package can only be build on Ubuntu Precise or Trusty"
-  exit 1
-fi
 
 # call cleanup() on exit
 trap cleanup 0
 process_opts "$@"
 BUILDDIR=${BUILDDIR:=$(readlink -f "${SCRIPTDIR}/../../../../out/Release")}
 
+if [[ "$(basename ${SYSROOT})" = "debian_wheezy_"*"-sysroot" ]]; then
+  TARGET_DISTRO="wheezy"
+elif [[ "$(basename ${SYSROOT})" = "debian_jessie_"*"-sysroot" ]]; then
+  TARGET_DISTRO="jessie"
+else
+  echo "Debian package can only be built using the wheezy or jessie sysroot."
+  exit 1
+fi
+
 source ${BUILDDIR}/installer/common/installer.include
 
 get_version_info
@@ -256,13 +265,6 @@
 # the LSB sub-packages, to avoid pulling in all that stuff that's not installed
 # by default.
 
-# Need a dummy debian/control file for dpkg-shlibdeps.
-DUMMY_STAGING_DIR="${TMPFILEDIR}/dummy_staging"
-mkdir "$DUMMY_STAGING_DIR"
-cd "$DUMMY_STAGING_DIR"
-mkdir debian
-touch debian/control
-
 # Generate the dependencies,
 # TODO(mmoss): This is a workaround for a problem where dpkg-shlibdeps was
 # resolving deps using some of our build output shlibs (i.e.
@@ -272,33 +274,41 @@
 # but it seems that we don't currently, so this is the most expediant fix.
 SAVE_LDLP=${LD_LIBRARY_PATH:-}
 unset LD_LIBRARY_PATH
-DPKG_SHLIB_DEPS=$(dpkg-shlibdeps -O "$BUILDDIR/chrome" | \
-  sed 's/^shlibs:Depends=//')
+if [ ${TARGETARCH} = "x64" ]; then
+  SHLIB_ARGS="-l${SYSROOT}/usr/lib/x86_64-linux-gnu"
+  SHLIB_ARGS="${SHLIB_ARGS} -l${SYSROOT}/lib/x86_64-linux-gnu"
+else
+  SHLIB_ARGS="-l${SYSROOT}/usr/lib/i386-linux-gnu"
+  SHLIB_ARGS="${SHLIB_ARGS} -l${SYSROOT}/lib/i386-linux-gnu"
+fi
+SHLIB_ARGS="${SHLIB_ARGS} -l${SYSROOT}/usr/lib"
+# TODO(thomasanderson): Unbundle dpkg-shlibdeps once the Precise->Trusty
+# transition is complete by reverting CL 2411423002 and applying ps40001.
+DPKG_SHLIB_DEPS=$(cd ${SYSROOT} && DPKG_DATADIR=${DPKG_DEV_DIR} \
+  perl -I ${DPKG_DEV_DIR}/scripts ${DPKG_DEV_DIR}/scripts/dpkg-shlibdeps.pl \
+  ${SHLIB_ARGS:-} -O -e"$BUILDDIR/chrome" | sed 's/^shlibs:Depends=//')
 if [ -n "$SAVE_LDLP" ]; then
   LD_LIBRARY_PATH=$SAVE_LDLP
 fi
 
 # Format it nicely and save it for comparison.
-# The grep -v is for a duplicate libc6 dep caused by Lucid glibc silliness.
-echo "$DPKG_SHLIB_DEPS" | sed 's/, /\n/g' | \
-  grep -v '^libc6 (>= 2.3.6-6~)$' | LANG=C sort > actual
+echo "$DPKG_SHLIB_DEPS" | sed 's/, /\n/g' | LANG=C sort > actual
 
-# Compare the expected dependency list to the generate list.
+# Compare the expected dependency list to the generated list.
 BAD_DIFF=0
-diff -u "$SCRIPTDIR/expected_deps_${TARGETARCH}_${HOST_DISTRO}" actual || \
+diff -u "$SCRIPTDIR/expected_deps_${TARGETARCH}_${TARGET_DISTRO}" actual || \
   BAD_DIFF=1
 if [ $BAD_DIFF -ne 0 ] && [ -z "${IGNORE_DEPS_CHANGES:-}" ]; then
   echo
   echo "ERROR: Shared library dependencies changed!"
   echo "If this is intentional, please update:"
-  echo "chrome/installer/linux/debian/expected_deps_ia32_precise"
-  echo "chrome/installer/linux/debian/expected_deps_ia32_trusty"
-  echo "chrome/installer/linux/debian/expected_deps_x64_precise"
-  echo "chrome/installer/linux/debian/expected_deps_x64_trusty"
+  echo "chrome/installer/linux/debian/expected_deps_ia32_jessie"
+  echo "chrome/installer/linux/debian/expected_deps_ia32_wheezy"
+  echo "chrome/installer/linux/debian/expected_deps_x64_jessie"
+  echo "chrome/installer/linux/debian/expected_deps_x64_wheezy"
   echo
   exit $BAD_DIFF
 fi
-rm -rf "$DUMMY_STAGING_DIR"
 
 # Additional dependencies not in the dpkg-shlibdeps output.
 # ca-certificates: Make sure users have SSL certificates.
diff --git a/chrome/installer/linux/debian/expected_deps_ia32_trusty b/chrome/installer/linux/debian/expected_deps_ia32_jessie
similarity index 100%
copy from chrome/installer/linux/debian/expected_deps_ia32_trusty
copy to chrome/installer/linux/debian/expected_deps_ia32_jessie
diff --git a/chrome/installer/linux/debian/expected_deps_ia32_precise b/chrome/installer/linux/debian/expected_deps_ia32_precise
deleted file mode 100644
index a9c3a7a..0000000
--- a/chrome/installer/linux/debian/expected_deps_ia32_precise
+++ /dev/null
@@ -1,32 +0,0 @@
-gconf-service
-libasound2 (>= 1.0.23)
-libatk1.0-0 (>= 1.12.4)
-libc6 (>= 2.11)
-libcairo2 (>= 1.6.0)
-libcups2 (>= 1.4.0)
-libdbus-1-3 (>= 1.2.14)
-libexpat1 (>= 1.95.8)
-libfontconfig1 (>= 2.8.0)
-libfreetype6 (>= 2.3.9)
-libgcc1 (>= 1:4.1.1)
-libgconf-2-4 (>= 2.31.1)
-libgdk-pixbuf2.0-0 (>= 2.22.0)
-libglib2.0-0 (>= 2.26.0)
-libgtk2.0-0 (>= 2.24.0)
-libnspr4 (>= 1.8.0.10)
-libnss3 (>= 3.12.4)
-libpango1.0-0 (>= 1.14.0)
-libstdc++6 (>= 4.6)
-libx11-6 (>= 2:1.4.99.1)
-libx11-xcb1
-libxcb1 (>= 1.6)
-libxcomposite1 (>= 1:0.3-1)
-libxcursor1 (>> 1.1.2)
-libxdamage1 (>= 1:1.1)
-libxext6
-libxfixes3
-libxi6 (>= 2:1.2.99.4)
-libxrandr2 (>= 2:1.2.99.3)
-libxrender1
-libxss1
-libxtst6
diff --git a/chrome/installer/linux/debian/expected_deps_ia32_trusty b/chrome/installer/linux/debian/expected_deps_ia32_wheezy
similarity index 100%
rename from chrome/installer/linux/debian/expected_deps_ia32_trusty
rename to chrome/installer/linux/debian/expected_deps_ia32_wheezy
diff --git a/chrome/installer/linux/debian/expected_deps_x64_trusty b/chrome/installer/linux/debian/expected_deps_x64_jessie
similarity index 81%
copy from chrome/installer/linux/debian/expected_deps_x64_trusty
copy to chrome/installer/linux/debian/expected_deps_x64_jessie
index 6e6d44c..87c2fc50 100644
--- a/chrome/installer/linux/debian/expected_deps_x64_trusty
+++ b/chrome/installer/linux/debian/expected_deps_x64_jessie
@@ -1,23 +1,23 @@
 gconf-service
 libasound2 (>= 1.0.16)
 libatk1.0-0 (>= 1.12.4)
-libc6 (>= 2.11)
+libc6 (>= 2.15)
 libcairo2 (>= 1.6.0)
 libcups2 (>= 1.4.0)
-libdbus-1-3 (>= 1.2.14)
+libdbus-1-3 (>= 1.1.4)
 libexpat1 (>= 2.0.1)
-libfontconfig1 (>= 2.9.0)
+libfontconfig1 (>= 2.11)
 libfreetype6 (>= 2.3.9)
 libgcc1 (>= 1:4.1.1)
-libgconf-2-4 (>= 2.31.1)
+libgconf-2-4 (>= 3.2.5)
 libgdk-pixbuf2.0-0 (>= 2.22.0)
-libglib2.0-0 (>= 2.26.0)
+libglib2.0-0 (>= 2.41.1)
 libgtk2.0-0 (>= 2.24.0)
 libnspr4 (>= 2:4.9-2~) | libnspr4-0d (>= 1.8.0.10)
 libnss3 (>= 2:3.13.4-2~) | libnss3-1d (>= 3.12.4)
 libpango-1.0-0 (>= 1.14.0)
 libpangocairo-1.0-0 (>= 1.14.0)
-libstdc++6 (>= 4.6)
+libstdc++6 (>= 4.8.1)
 libx11-6 (>= 2:1.4.99.1)
 libx11-xcb1
 libxcb1 (>= 1.6)
diff --git a/chrome/installer/linux/debian/expected_deps_x64_precise b/chrome/installer/linux/debian/expected_deps_x64_precise
deleted file mode 100644
index a5f66cb..0000000
--- a/chrome/installer/linux/debian/expected_deps_x64_precise
+++ /dev/null
@@ -1,32 +0,0 @@
-gconf-service
-libasound2 (>= 1.0.23)
-libatk1.0-0 (>= 1.12.4)
-libc6 (>= 2.11)
-libcairo2 (>= 1.6.0)
-libcups2 (>= 1.4.0)
-libdbus-1-3 (>= 1.1.4)
-libexpat1 (>= 1.95.8)
-libfontconfig1 (>= 2.8.0)
-libfreetype6 (>= 2.3.9)
-libgcc1 (>= 1:4.1.1)
-libgconf-2-4 (>= 2.31.1)
-libgdk-pixbuf2.0-0 (>= 2.22.0)
-libglib2.0-0 (>= 2.26.0)
-libgtk2.0-0 (>= 2.24.0)
-libnspr4 (>= 1.8.0.10)
-libnss3 (>= 3.12.4)
-libpango1.0-0 (>= 1.14.0)
-libstdc++6 (>= 4.6)
-libx11-6 (>= 2:1.4.99.1)
-libx11-xcb1
-libxcb1 (>= 1.6)
-libxcomposite1 (>= 1:0.3-1)
-libxcursor1 (>> 1.1.2)
-libxdamage1 (>= 1:1.1)
-libxext6
-libxfixes3
-libxi6 (>= 2:1.2.99.4)
-libxrandr2 (>= 2:1.2.99.3)
-libxrender1
-libxss1
-libxtst6
diff --git a/chrome/installer/linux/debian/expected_deps_x64_trusty b/chrome/installer/linux/debian/expected_deps_x64_wheezy
similarity index 88%
rename from chrome/installer/linux/debian/expected_deps_x64_trusty
rename to chrome/installer/linux/debian/expected_deps_x64_wheezy
index 6e6d44c..b23857d 100644
--- a/chrome/installer/linux/debian/expected_deps_x64_trusty
+++ b/chrome/installer/linux/debian/expected_deps_x64_wheezy
@@ -4,7 +4,7 @@
 libc6 (>= 2.11)
 libcairo2 (>= 1.6.0)
 libcups2 (>= 1.4.0)
-libdbus-1-3 (>= 1.2.14)
+libdbus-1-3 (>= 1.1.4)
 libexpat1 (>= 2.0.1)
 libfontconfig1 (>= 2.9.0)
 libfreetype6 (>= 2.3.9)
@@ -15,8 +15,7 @@
 libgtk2.0-0 (>= 2.24.0)
 libnspr4 (>= 2:4.9-2~) | libnspr4-0d (>= 1.8.0.10)
 libnss3 (>= 2:3.13.4-2~) | libnss3-1d (>= 3.12.4)
-libpango-1.0-0 (>= 1.14.0)
-libpangocairo-1.0-0 (>= 1.14.0)
+libpango1.0-0 (>= 1.14.0)
 libstdc++6 (>= 4.6)
 libx11-6 (>= 2:1.4.99.1)
 libx11-xcb1
diff --git a/chrome/test/data/webui/settings/appearance_page_test.js b/chrome/test/data/webui/settings/appearance_page_test.js
index 4ade4fc..49b4909 100644
--- a/chrome/test/data/webui/settings/appearance_page_test.js
+++ b/chrome/test/data/webui/settings/appearance_page_test.js
@@ -15,6 +15,7 @@
   var TestAppearanceBrowserProxy = function() {
     settings.TestBrowserProxy.call(this, [
       'getThemeInfo',
+      'isSupervised',
       'openWallpaperManager',
       'useDefaultTheme',
       'useSystemTheme',
@@ -24,6 +25,9 @@
   TestAppearanceBrowserProxy.prototype = {
     __proto__: settings.TestBrowserProxy.prototype,
 
+    /** @private */
+    isSupervised_: false,
+
     /** @override */
     getThemeInfo: function(themeId) {
       this.methodCalled('getThemeInfo', themeId);
@@ -31,6 +35,12 @@
     },
 
     /** @override */
+    isSupervised: function() {
+      this.methodCalled('isSupervised');
+      return this.isSupervised_;
+    },
+
+    /** @override */
     openWallpaperManager: function() {
       this.methodCalled('openWallpaperManager');
     },
@@ -44,6 +54,11 @@
     useSystemTheme: function() {
       this.methodCalled('useSystemTheme');
     },
+
+    /** @param {boolean} Whether the user is supervised */
+    setIsSupervised: function(isSupervised) {
+      this.isSupervised_ = isSupervised;
+    },
   };
 
   /**
@@ -105,7 +120,7 @@
           extensions: {
             theme: {
               id: {
-                value: 'asdf',
+                value: '',
               },
               use_system: {
                 value: false,
@@ -134,20 +149,78 @@
         });
       }
 
-      test('useDefaultTheme', function() {
-        var button = appearancePage.$$('#useDefault');
-        assertTrue(!!button);
-        MockInteractions.tap(button);
-        return appearanceBrowserProxy.whenCalled('useDefaultTheme');
-      });
+      var THEME_ID_PREF = 'prefs.extensions.theme.id.value';
 
       if (cr.isLinux && !cr.isChromeOS) {
-        test('useSystemTheme', function() {
+        var USE_SYSTEM_PREF = 'prefs.extensions.theme.use_system.value';
+
+        test('useDefaultThemeLinux', function() {
+          assertFalse(!!appearancePage.get(THEME_ID_PREF));
+          assertFalse(appearancePage.get(USE_SYSTEM_PREF));
+          // No custom nor system theme in use; "USE CLASSIC" should be hidden.
+          assertFalse(!!appearancePage.$$('#useDefault'));
+
+          appearancePage.set(USE_SYSTEM_PREF, true);
+          Polymer.dom.flush();
+          // If the system theme is in use, "USE CLASSIC" should show.
+          assertTrue(!!appearancePage.$$('#useDefault'));
+
+          appearancePage.set(USE_SYSTEM_PREF, false);
+          appearancePage.set(THEME_ID_PREF, 'fake theme id');
+          Polymer.dom.flush();
+
+          // With a custom theme installed, "USE CLASSIC" should show.
+          var button = appearancePage.$$('#useDefault');
+          assertTrue(!!button);
+
+          MockInteractions.tap(button);
+          return appearanceBrowserProxy.whenCalled('useDefaultTheme');
+        });
+
+        test('useSystemThemeLinux', function() {
+          assertFalse(!!appearancePage.get(THEME_ID_PREF));
+          appearancePage.set(USE_SYSTEM_PREF, true);
+          Polymer.dom.flush();
+          // The "USE GTK+" button shouldn't be showing if it's already in use.
+          assertFalse(!!appearancePage.$$('#useSystem'));
+
+          appearanceBrowserProxy.setIsSupervised(true);
+          appearancePage.set(USE_SYSTEM_PREF, false);
+          Polymer.dom.flush();
+          // Supervised users have their own theme and can't use GTK+ theme.
+          assertFalse(!!appearancePage.$$('#useDefault'));
+          assertFalse(!!appearancePage.$$('#useSystem'));
+          // If there's no "USE" buttons, the container should be hidden.
+          assertTrue(appearancePage.$$('.secondary-action').hidden);
+
+          appearanceBrowserProxy.setIsSupervised(false);
+          appearancePage.set(THEME_ID_PREF, 'fake theme id');
+          Polymer.dom.flush();
+          // If there's "USE" buttons again, the container should be visible.
+          assertTrue(!!appearancePage.$$('#useDefault'));
+          assertFalse(appearancePage.$$('.secondary-action').hidden);
+
           var button = appearancePage.$$('#useSystem');
           assertTrue(!!button);
+
           MockInteractions.tap(button);
           return appearanceBrowserProxy.whenCalled('useSystemTheme');
         });
+      } else {
+        test('useDefaultTheme', function() {
+          assertFalse(!!appearancePage.get(THEME_ID_PREF));
+          assertFalse(!!appearancePage.$$('#useDefault'));
+
+          appearancePage.set(THEME_ID_PREF, 'fake theme id');
+          Polymer.dom.flush();
+
+          // With a custom theme installed, "RESET TO DEFAULT" should show.
+          var button = appearancePage.$$('#useDefault');
+          assertTrue(!!button);
+
+          MockInteractions.tap(button);
+          return appearanceBrowserProxy.whenCalled('useDefaultTheme');
+        });
       }
     });
   }
diff --git a/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js b/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
index c3b7c61..988b092d 100644
--- a/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
+++ b/chrome/test/data/webui/settings/settings_autofill_section_browsertest.js
@@ -9,8 +9,7 @@
 
 // Polymer BrowserTest fixture.
 GEN_INCLUDE([
-    ROOT_PATH + 'chrome/test/data/webui/polymer_browser_test_base.js',
-    ROOT_PATH + 'ui/webui/resources/js/load_time_data.js',
+  ROOT_PATH + 'chrome/test/data/webui/polymer_browser_test_base.js',
 ]);
 
 /**
@@ -24,9 +23,9 @@
   getCountryList: function() {
     return new Promise(function(resolve) {
       resolve([
-          {name: 'United States', countryCode: 'US'},  // Default test country.
-          {name: 'Israel', countryCode: 'IL'},
-          {name: 'United Kingdom', countryCode: 'GB'},
+        {name: 'United States', countryCode: 'US'},  // Default test country.
+        {name: 'Israel', countryCode: 'IL'},
+        {name: 'United Kingdom', countryCode: 'GB'},
       ]);
     });
   },
@@ -47,7 +46,7 @@
  * @return {!Promise}
  */
 function asyncForEach(items, loopBody) {
-  return new Promise(function(finish) {
+  return new Promise(function(resolve) {
     var index = 0;
 
     function loop() {
@@ -55,7 +54,7 @@
       if (item)
         loopBody(item).then(loop);
       else
-        finish();
+        resolve();
     };
 
     loop();
@@ -108,13 +107,6 @@
    */
   runAccessibilityChecks: false,
 
-  i18nStrings: {
-    addAddressTitle: 'add-title',
-    addCreditCardTitle: 'add-title',
-    editAddressTitle: 'edit-title',
-    editCreditCardTitle: 'edit-title',
-  },
-
   /** @override */
   setUp: function() {
     PolymerTest.prototype.setUp.call(this);
@@ -122,9 +114,6 @@
     // Test is run on an individual element that won't have a page language.
     this.accessibilityAuditConfig.auditRulesToIgnore.push('humanLangMissing');
 
-    // Faking 'strings.js' for this test.
-    loadTimeData.data = this.i18nStrings;
-
     settings.address.CountryDetailManagerImpl.instance_ =
         new CountryDetailManagerTestImpl();
   },
@@ -410,7 +399,7 @@
       return self.createAddressDialog_(
           FakeDataMaker.emptyAddressEntry()).then(function(dialog) {
         var title = dialog.$$('.title');
-        assertEquals(self.i18nStrings.addAddressTitle, title.textContent);
+        assertEquals(loadTimeData.getString('addAddress'), title.textContent);
         // Shouldn't be possible to save until something is typed in.
         assertTrue(dialog.$.saveButton.disabled);
       });
@@ -420,7 +409,8 @@
       return self.createAddressDialog_(
           FakeDataMaker.addressEntry()).then(function(dialog) {
         var title = dialog.$$('.title');
-        assertEquals(self.i18nStrings.editAddressTitle, title.textContent);
+        assertEquals(
+            loadTimeData.getString('editAddressTitle'), title.textContent);
         // Should be possible to save when editing because fields are populated.
         assertFalse(dialog.$.saveButton.disabled);
       });
diff --git a/chrome/test/data/webui/settings/site_list_tests.js b/chrome/test/data/webui/settings/site_list_tests.js
index 72cf641..cf8137c 100644
--- a/chrome/test/data/webui/settings/site_list_tests.js
+++ b/chrome/test/data/webui/settings/site_list_tests.js
@@ -401,7 +401,6 @@
 
               assertEquals(
                   settings.PermissionValues.ALLOW, testElement.categorySubtype);
-              assertEquals('Allow - 0', testElement.$.header.innerText.trim());
 
               assertFalse(testElement.$.category.hidden);
               browserProxy.resetResolver('getExceptionList');
@@ -409,8 +408,6 @@
               return browserProxy.whenCalled('getExceptionList');
             }).then(function(contentType) {
               assertFalse(testElement.$.category.hidden);
-              assertEquals('Exceptions - 0',
-                  testElement.$.header.innerText.trim());
             });
       });
 
@@ -430,7 +427,6 @@
               Polymer.dom.flush();  // Populates action menu.
               openActionMenu(0);
               assertMenu(['Block', 'Remove'], testElement);
-              assertEquals('Allow - 2', testElement.$.header.innerText.trim());
 
               // Site list should show, no matter what category default is set
               // to.
@@ -440,8 +436,6 @@
               return browserProxy.whenCalled('getExceptionList');
             }).then(function(contentType) {
               assertFalse(testElement.$.category.hidden);
-              assertEquals('Exceptions - 2',
-                  testElement.$.header.innerText.trim());
             });
       });
 
@@ -462,7 +456,6 @@
               Polymer.dom.flush();  // Populates action menu.
               openActionMenu(0);
               assertMenu(['Allow', 'Remove'], testElement);
-              assertEquals('Block - 2', testElement.$.header.innerText.trim());
 
               // Site list should only show when category default is enabled.
               assertFalse(testElement.$.category.hidden);
@@ -491,8 +484,6 @@
               Polymer.dom.flush();  // Populates action menu.
               openActionMenu(0);
               assertMenu(['Allow', 'Block', 'Remove'], testElement);
-              assertEquals('Clear on exit - 1',
-                  testElement.$.header.innerText.trim());
 
               // Site list should show, no matter what category default is set
               // to.
@@ -502,7 +493,6 @@
               return browserProxy.whenCalled('getExceptionList');
             }).then(function(contentType) {
               assertFalse(testElement.$.category.hidden);
-              assertEquals('Clear on exit - 1', testElement.$.header.innerText);
             });
       });
 
@@ -523,8 +513,6 @@
               openActionMenu(0);
               // 'Clear on exit' is visible as this is not an incognito item.
               assertMenu(['Allow', 'Clear on exit', 'Remove'], testElement);
-              assertEquals('Block - 1',
-                  testElement.$.header.innerText.trim());
 
               // Select 'Remove from menu'.
               var menuItems = getMenuItems(testElement.$.listContainer, 0);
@@ -560,8 +548,6 @@
               openActionMenu(0);
               // 'Clear on exit' is hidden for incognito items.
               assertMenu(['Block', 'Remove'], testElement);
-              assertEquals('Allow - 2',
-                  testElement.$.header.innerText.trim());
 
               // Select 'Remove' from menu on 'foo.com'.
               openActionMenu(1);
diff --git a/chrome/tools/build/win/FILES.cfg b/chrome/tools/build/win/FILES.cfg
index 163f0b86..877890e 100644
--- a/chrome/tools/build/win/FILES.cfg
+++ b/chrome/tools/build/win/FILES.cfg
@@ -388,7 +388,6 @@
   },
   {
     'filename': 'remoting-me2me-host-win.zip',
-    'arch': ['32bit'],
     'buildtype': ['dev', 'official'],
     'archive': 'remoting-me2me-host-win.zip',
     'direct_archive': 1,
@@ -396,42 +395,36 @@
   },
   {
     'filename': 'remoting_core.dll',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
     'filegroup': ['symsrc'],
   },
   {
     'filename': 'remoting_core.dll.pdb',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
     'optional': ['official'],
   },
   {
     'filename': 'remoting_desktop.exe',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
     'filegroup': ['symsrc'],
   },
   {
     'filename': 'remoting_desktop.exe.pdb',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
     'optional': ['official'],
   },
   {
     'filename': 'remoting_host.exe',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
     'filegroup': ['symsrc'],
   },
   {
     'filename': 'remoting_host.exe.pdb',
-    'arch': ['32bit'],
     'buildtype': ['official'],
     'archive': 'remoting-win32.zip',
   },
@@ -605,7 +598,7 @@
     'arch': ['32bit'],
     'buildtype': ['dev', 'official'],
     'archive': 'chrome-win32-syms.zip',
-    'optional': ['dev'],
+    'optional': ['dev', 'official'],
   },
   {
     'filename': 'syzygy/chrome_child.dll.pdb',
@@ -619,7 +612,7 @@
     'arch': ['32bit'],
     'buildtype': ['dev', 'official'],
     'archive': 'chrome-win32-syms.zip',
-    'optional': ['dev'],
+    'optional': ['dev', 'official'],
   },
   {
     'filename': 'syzygy/syzyasan_rtl.dll.pdb',
diff --git a/chromeos/dbus/debug_daemon_client.cc b/chromeos/dbus/debug_daemon_client.cc
index ed83a06b..eda3a10 100644
--- a/chromeos/dbus/debug_daemon_client.cc
+++ b/chromeos/dbus/debug_daemon_client.cc
@@ -120,7 +120,7 @@
     writer.AppendBool(is_compressed);
     writer.AppendFileDescriptor(file_descriptor);
     debugdaemon_proxy_->CallMethod(
-        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+        &method_call, kBigLogsDBusTimeoutMS,
         base::Bind(&DebugDaemonClientImpl::OnGetDebugLogs,
                    weak_ptr_factory_.GetWeakPtr(), callback));
   }
diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc
index d2433bd..a399809 100644
--- a/components/bookmarks/browser/bookmark_model.cc
+++ b/components/bookmarks/browser/bookmark_model.cc
@@ -129,8 +129,8 @@
 }
 
 BookmarkModel::~BookmarkModel() {
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkModelBeingDeleted(this));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkModelBeingDeleted(this);
 
   if (store_.get()) {
     // The store maintains a reference back to us. We need to tell it we're gone
@@ -178,8 +178,8 @@
 
 void BookmarkModel::BeginExtensiveChanges() {
   if (++extensive_changes_ == 1) {
-    FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                      ExtensiveBookmarkChangesBeginning(this));
+    for (BookmarkModelObserver& observer : observers_)
+      observer.ExtensiveBookmarkChangesBeginning(this);
   }
 }
 
@@ -187,19 +187,19 @@
   --extensive_changes_;
   DCHECK_GE(extensive_changes_, 0);
   if (extensive_changes_ == 0) {
-    FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                      ExtensiveBookmarkChangesEnded(this));
+    for (BookmarkModelObserver& observer : observers_)
+      observer.ExtensiveBookmarkChangesEnded(this);
   }
 }
 
 void BookmarkModel::BeginGroupedChanges() {
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    GroupedBookmarkChangesBeginning(this));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.GroupedBookmarkChangesBeginning(this);
 }
 
 void BookmarkModel::EndGroupedChanges() {
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    GroupedBookmarkChangesEnded(this));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.GroupedBookmarkChangesEnded(this);
 }
 
 void BookmarkModel::Remove(const BookmarkNode* node) {
@@ -218,8 +218,8 @@
   };
   std::vector<RemoveNodeData> removed_node_data_list;
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillRemoveAllUserBookmarks(this));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillRemoveAllUserBookmarks(this);
 
   BeginExtensiveChanges();
   // Skip deleting permanent nodes. Permanent bookmark nodes are the root and
@@ -244,8 +244,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkAllUserNodesRemoved(this, removed_urls));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkAllUserNodesRemoved(this, removed_urls);
 
   BeginGroupedChanges();
   for (auto& removed_node_data : removed_node_data_list) {
@@ -294,9 +294,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeMoved(this, old_parent, old_index,
-                                      new_parent, index));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeMoved(this, old_parent, old_index, new_parent, index);
 }
 
 void BookmarkModel::Copy(const BookmarkNode* node,
@@ -352,8 +351,8 @@
     return;
   }
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillChangeBookmarkNode(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillChangeBookmarkNode(this, node);
 
   // The title index doesn't support changing the title, instead we remove then
   // add it back.
@@ -364,8 +363,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeChanged(this, node);
 }
 
 void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
@@ -378,8 +377,8 @@
   mutable_node->InvalidateFavicon();
   CancelPendingFaviconLoadRequests(mutable_node);
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillChangeBookmarkNode(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillChangeBookmarkNode(this, node);
 
   {
     base::AutoLock url_lock(url_lock_);
@@ -391,8 +390,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeChanged(this, node);
 }
 
 void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
@@ -402,14 +401,14 @@
   if (node->GetMetaInfo(key, &old_value) && old_value == value)
     return;
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillChangeBookmarkMetaInfo(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillChangeBookmarkMetaInfo(this, node);
 
   if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkMetaInfoChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkMetaInfoChanged(this, node);
 }
 
 void BookmarkModel::SetNodeMetaInfoMap(
@@ -420,15 +419,15 @@
       (old_meta_info_map && meta_info_map == *old_meta_info_map))
     return;
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillChangeBookmarkMetaInfo(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillChangeBookmarkMetaInfo(this, node);
 
   AsMutable(node)->SetMetaInfoMap(meta_info_map);
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkMetaInfoChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkMetaInfoChanged(this, node);
 }
 
 void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
@@ -437,14 +436,14 @@
   if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
     return;
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillChangeBookmarkMetaInfo(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillChangeBookmarkMetaInfo(this, node);
 
   if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkMetaInfoChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkMetaInfoChanged(this, node);
 }
 
 void BookmarkModel::AddNonClonedKey(const std::string& key) {
@@ -492,9 +491,8 @@
     BookmarkNode* mutable_node = AsMutable(node);
     mutable_node->InvalidateFavicon();
     CancelPendingFaviconLoadRequests(mutable_node);
-    FOR_EACH_OBSERVER(BookmarkModelObserver,
-                      observers_,
-                      BookmarkNodeFaviconChanged(this, node));
+    for (BookmarkModelObserver& observer : observers_)
+      observer.BookmarkNodeFaviconChanged(this, node);
   }
 }
 
@@ -650,8 +648,8 @@
     return;
   }
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillReorderBookmarkNode(this, parent));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillReorderBookmarkNode(this, parent);
 
   UErrorCode error = U_ZERO_ERROR;
   std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
@@ -665,8 +663,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeChildrenReordered(this, parent));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeChildrenReordered(this, parent);
 }
 
 void BookmarkModel::ReorderChildren(
@@ -679,8 +677,8 @@
   for (const BookmarkNode* node : ordered_nodes)
     DCHECK_EQ(parent, node->parent());
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillReorderBookmarkNode(this, parent));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillReorderBookmarkNode(this, parent);
 
   if (ordered_nodes.size() > 1) {
     std::map<const BookmarkNode*, int> order;
@@ -700,8 +698,8 @@
       store_->ScheduleSave();
   }
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeChildrenReordered(this, parent));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeChildrenReordered(this, parent);
 }
 
 void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
@@ -774,8 +772,8 @@
 
 void BookmarkModel::NotifyNodeAddedForAllDescendents(const BookmarkNode* node) {
   for (int i = 0; i < node->child_count(); ++i) {
-    FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                      BookmarkNodeAdded(this, node, i));
+    for (BookmarkModelObserver& observer : observers_)
+      observer.BookmarkNodeAdded(this, node, i);
     NotifyNodeAddedForAllDescendents(node->GetChild(i));
   }
 }
@@ -900,8 +898,8 @@
       FROM_HERE_WITH_EXPLICIT_FUNCTION("467179 BookmarkModel::DoneLoading6"));
 
   // Notify our direct observers.
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkModelLoaded(this, details->ids_reassigned()));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkModelLoaded(this, details->ids_reassigned());
 }
 
 void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* node_ptr) {
@@ -912,8 +910,8 @@
   int index = parent->GetIndexOf(node_ptr);
   DCHECK_NE(-1, index);
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    OnWillRemoveBookmarks(this, parent, index, node_ptr));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.OnWillRemoveBookmarks(this, parent, index, node_ptr);
 
   std::set<GURL> removed_urls;
   {
@@ -924,10 +922,8 @@
   if (store_.get())
     store_->ScheduleSave();
 
-  FOR_EACH_OBSERVER(
-      BookmarkModelObserver,
-      observers_,
-      BookmarkNodeRemoved(this, parent, index, node.get(), removed_urls));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeRemoved(this, parent, index, node.get(), removed_urls);
 
   undo_delegate()->OnBookmarkNodeRemoved(this, parent, index, std::move(node));
 }
@@ -989,8 +985,8 @@
     AddNodeToInternalMaps(node_ptr);
   }
 
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeAdded(this, parent, index));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeAdded(this, parent, index);
 
   return node_ptr;
 }
@@ -1084,8 +1080,8 @@
 }
 
 void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
-  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
-                    BookmarkNodeFaviconChanged(this, node));
+  for (BookmarkModelObserver& observer : observers_)
+    observer.BookmarkNodeFaviconChanged(this, node);
 }
 
 void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index 81dcf3f3..0a29998 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -529,29 +529,29 @@
 }
 
 void ProfileSyncService::OnProtocolEvent(const syncer::ProtocolEvent& event) {
-  FOR_EACH_OBSERVER(ProtocolEventObserver, protocol_event_observers_,
-                    OnProtocolEvent(event));
+  for (auto& observer : protocol_event_observers_)
+    observer.OnProtocolEvent(event);
 }
 
 void ProfileSyncService::OnDirectoryTypeCommitCounterUpdated(
     syncer::ModelType type,
     const syncer::CommitCounters& counters) {
-  FOR_EACH_OBSERVER(syncer::TypeDebugInfoObserver, type_debug_info_observers_,
-                    OnCommitCountersUpdated(type, counters));
+  for (auto& observer : type_debug_info_observers_)
+    observer.OnCommitCountersUpdated(type, counters);
 }
 
 void ProfileSyncService::OnDirectoryTypeUpdateCounterUpdated(
     syncer::ModelType type,
     const syncer::UpdateCounters& counters) {
-  FOR_EACH_OBSERVER(syncer::TypeDebugInfoObserver, type_debug_info_observers_,
-                    OnUpdateCountersUpdated(type, counters));
+  for (auto& observer : type_debug_info_observers_)
+    observer.OnUpdateCountersUpdated(type, counters);
 }
 
 void ProfileSyncService::OnDatatypeStatusCounterUpdated(
     syncer::ModelType type,
     const syncer::StatusCounters& counters) {
-  FOR_EACH_OBSERVER(syncer::TypeDebugInfoObserver, type_debug_info_observers_,
-                    OnStatusCountersUpdated(type, counters));
+  for (auto& observer : type_debug_info_observers_)
+    observer.OnStatusCountersUpdated(type, counters);
 }
 
 void ProfileSyncService::OnDataTypeRequestsSyncStartup(syncer::ModelType type) {
@@ -831,17 +831,18 @@
 }
 
 void ProfileSyncService::NotifyObservers() {
-  FOR_EACH_OBSERVER(syncer::SyncServiceObserver, observers_, OnStateChanged());
+  for (auto& observer : observers_)
+    observer.OnStateChanged();
 }
 
 void ProfileSyncService::NotifySyncCycleCompleted() {
-  FOR_EACH_OBSERVER(syncer::SyncServiceObserver, observers_,
-                    OnSyncCycleCompleted());
+  for (auto& observer : observers_)
+    observer.OnSyncCycleCompleted();
 }
 
 void ProfileSyncService::NotifyForeignSessionUpdated() {
-  FOR_EACH_OBSERVER(syncer::SyncServiceObserver, observers_,
-                    OnForeignSessionUpdated());
+  for (auto& observer : observers_)
+    observer.OnForeignSessionUpdated();
 }
 
 void ProfileSyncService::ClearStaleErrors() {
@@ -1329,8 +1330,8 @@
   }
 
   // Notify listeners that configuration is done.
-  FOR_EACH_OBSERVER(syncer::SyncServiceObserver, observers_,
-                    OnSyncConfigurationCompleted());
+  for (auto& observer : observers_)
+    observer.OnSyncConfigurationCompleted();
 
   DVLOG(1) << "PSS OnConfigureDone called with status: " << configure_status_;
   // The possible status values:
diff --git a/components/invalidation/impl/invalidation_logger.cc b/components/invalidation/impl/invalidation_logger.cc
index d25d0b1e..cfb5ca0c 100644
--- a/components/invalidation/impl/invalidation_logger.cc
+++ b/components/invalidation/impl/invalidation_logger.cc
@@ -33,8 +33,8 @@
 }
 
 void InvalidationLogger::EmitRegisteredHandlers() {
-  FOR_EACH_OBSERVER(InvalidationLoggerObserver, observer_list_,
-                    OnRegistrationChange(registered_handlers_));
+  for (auto& observer : observer_list_)
+    observer.OnRegistrationChange(registered_handlers_);
 }
 
 void InvalidationLogger::OnStateChange(
@@ -47,10 +47,10 @@
 }
 
 void InvalidationLogger::EmitState() {
-  FOR_EACH_OBSERVER(InvalidationLoggerObserver,
-                    observer_list_,
-                    OnStateChange(last_invalidator_state_,
-                                  last_invalidator_state_timestamp_));
+  for (auto& observer : observer_list_) {
+    observer.OnStateChange(last_invalidator_state_,
+                           last_invalidator_state_timestamp_);
+  }
 }
 
 void InvalidationLogger::OnUpdateIds(
@@ -73,15 +73,14 @@
          ++oid_it) {
       per_object_invalidation_count[*oid_it] = invalidation_count_[*oid_it];
     }
-    FOR_EACH_OBSERVER(InvalidationLoggerObserver,
-                      observer_list_,
-                      OnUpdateIds(it->first, per_object_invalidation_count));
+    for (auto& observer : observer_list_)
+      observer.OnUpdateIds(it->first, per_object_invalidation_count);
   }
 }
 
 void InvalidationLogger::OnDebugMessage(const base::DictionaryValue& details) {
-  FOR_EACH_OBSERVER(
-      InvalidationLoggerObserver, observer_list_, OnDebugMessage(details));
+  for (auto& observer : observer_list_)
+    observer.OnDebugMessage(details);
 }
 
 void InvalidationLogger::OnInvalidation(
@@ -94,8 +93,8 @@
        ++it) {
     invalidation_count_[it->object_id()]++;
   }
-  FOR_EACH_OBSERVER(
-      InvalidationLoggerObserver, observer_list_, OnInvalidation(details));
+  for (auto& observer : observer_list_)
+    observer.OnInvalidation(details);
 }
 
 void InvalidationLogger::EmitContent() {
diff --git a/components/invalidation/impl/invalidator_registrar.cc b/components/invalidation/impl/invalidator_registrar.cc
index 903cf72..f972790 100644
--- a/components/invalidation/impl/invalidator_registrar.cc
+++ b/components/invalidation/impl/invalidator_registrar.cc
@@ -115,8 +115,8 @@
   DVLOG(1) << "New invalidator state: " << InvalidatorStateToString(state_)
       << " -> " << InvalidatorStateToString(state);
   state_ = state;
-  FOR_EACH_OBSERVER(InvalidationHandler, handlers_,
-                    OnInvalidatorStateChange(state));
+  for (auto& observer : handlers_)
+    observer.OnInvalidatorStateChange(state);
 }
 
 InvalidatorState InvalidatorRegistrar::GetInvalidatorState() const {
diff --git a/components/invalidation/impl/sync_system_resources.cc b/components/invalidation/impl/sync_system_resources.cc
index f4f04e0..3cd65e7 100644
--- a/components/invalidation/impl/sync_system_resources.cc
+++ b/components/invalidation/impl/sync_system_resources.cc
@@ -193,8 +193,8 @@
 
 void SyncNetworkChannel::NotifyChannelStateChange(
     InvalidatorState invalidator_state) {
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnNetworkChannelStateChanged(invalidator_state));
+  for (auto& observer : observers_)
+    observer.OnNetworkChannelStateChanged(invalidator_state);
 }
 
 bool SyncNetworkChannel::DeliverIncomingMessage(const std::string& message) {
diff --git a/components/invalidation/impl/ticl_settings_provider.cc b/components/invalidation/impl/ticl_settings_provider.cc
index 6c9c632..c8b7adc 100644
--- a/components/invalidation/impl/ticl_settings_provider.cc
+++ b/components/invalidation/impl/ticl_settings_provider.cc
@@ -24,7 +24,8 @@
 }
 
 void TiclSettingsProvider::FireOnUseGCMChannelChanged() {
-  FOR_EACH_OBSERVER(Observer, observers_, OnUseGCMChannelChanged());
+  for (auto& observer : observers_)
+    observer.OnUseGCMChannelChanged();
 }
 
 }  // namespace invalidation
diff --git a/components/metrics/leak_detector/call_stack_table.cc b/components/metrics/leak_detector/call_stack_table.cc
index 8f9540bc0..ec9b7869 100644
--- a/components/metrics/leak_detector/call_stack_table.cc
+++ b/components/metrics/leak_detector/call_stack_table.cc
@@ -42,7 +42,7 @@
 CallStackTable::~CallStackTable() {}
 
 void CallStackTable::Add(const CallStack* call_stack) {
-  ++entry_map_[call_stack];
+  ++entry_map_[call_stack].count;
   ++num_allocs_;
 }
 
@@ -50,7 +50,7 @@
   auto iter = entry_map_.find(call_stack);
   if (iter == entry_map_.end())
     return;
-  uint32_t& count_for_call_stack = iter->second;
+  uint32_t& count_for_call_stack = iter->second.count;
   --count_for_call_stack;
   ++num_frees_;
 
@@ -67,9 +67,40 @@
 }
 
 void CallStackTable::GetTopCallStacks(RankedSet* top_entries) const {
-  for (const auto& call_stack_and_count : entry_map_) {
-    top_entries->AddCallStack(call_stack_and_count.first,
-                              call_stack_and_count.second);
+  for (const auto& call_stack_count_info : entry_map_) {
+    top_entries->AddCallStack(call_stack_count_info.first,
+                              call_stack_count_info.second.count);
+  }
+}
+
+void CallStackTable::UpdateLastDropInfo(size_t timestamp) {
+  for (auto& call_stack_and_info : entry_map_) {
+    auto& count_info = call_stack_and_info.second;
+
+    // If the |previous_count| is 0 then we need to initialize info.
+    if (count_info.previous_count == 0 ||
+        count_info.count < count_info.previous_count) {
+      count_info.last_drop_timestamp = timestamp;
+      count_info.last_drop_count = count_info.count;
+    }
+    count_info.previous_count = count_info.count;
+  }
+}
+
+void CallStackTable::GetLastUptrendInfo(
+    const CallStack* call_stack, size_t timestamp, size_t* timestamp_delta,
+    uint32_t* count_delta) const {
+  const auto& call_stack_count_info_iter = entry_map_.find(call_stack);
+
+  if (call_stack_count_info_iter != entry_map_.end()) {
+    const auto& count_info = call_stack_count_info_iter->second;
+    *timestamp_delta = timestamp - count_info.last_drop_timestamp;
+    *count_delta = count_info.count - count_info.last_drop_count;
+  } else {
+    DLOG(WARNING) << "Accessing information about a call stack that has not "
+                  << "been recorded";
+    *timestamp_delta = timestamp;
+    *count_delta = 0;
   }
 }
 
diff --git a/components/metrics/leak_detector/call_stack_table.h b/components/metrics/leak_detector/call_stack_table.h
index 77a4b2a..8372716 100644
--- a/components/metrics/leak_detector/call_stack_table.h
+++ b/components/metrics/leak_detector/call_stack_table.h
@@ -48,6 +48,21 @@
   // must already be initialized with that number.
   void GetTopCallStacks(RankedSet* top_entries) const;
 
+  // Updates information about the last seen drop in the number of allocations
+  // and sets the |previous_count| for every stored call stack, both inside
+  // |entry_map_|. It should be called in the analysis phase, before the
+  // reports are generated, with |timestamp| describing current point in the
+  // timeline.
+  void UpdateLastDropInfo(size_t timestamp);
+
+  // Retrieves the change in timestamp and allocation count since the last
+  // observed drop in the number for allocations for the given call stack
+  // (or since the oldest kept record for the call stacks if there were no
+  // drops)
+  void GetLastUptrendInfo(
+      const CallStack* call_stack, size_t timestamp, size_t *timestamp_delta,
+      uint32_t *count_delta) const;
+
   const LeakAnalyzer& leak_analyzer() const { return leak_analyzer_; }
 
   size_t size() const { return entry_map_.size(); }
@@ -57,6 +72,13 @@
   uint32_t num_frees() const { return num_frees_; }
 
  private:
+  struct CallStackCountInfo {
+    uint32_t count, previous_count, last_drop_count;
+    size_t last_drop_timestamp;
+    CallStackCountInfo() : count(0), previous_count(0), last_drop_count(0),
+                           last_drop_timestamp(0) {}
+  };
+
   // Total number of allocs and frees in this table.
   uint32_t num_allocs_;
   uint32_t num_frees_;
@@ -64,13 +86,13 @@
   // Hash table containing entries. Uses CustomAllocator to avoid recursive
   // malloc hook invocation when analyzing allocs and frees.
   using TableEntryAllocator =
-      STLAllocator<std::pair<const CallStack* const, uint32_t>,
+      STLAllocator<std::pair<const CallStack* const, CallStackCountInfo>,
                    CustomAllocator>;
 
   // Stores a mapping of each call stack to the number of recorded allocations
   // made from that call site.
   base::hash_map<const CallStack*,
-                 uint32_t,
+                 CallStackCountInfo,
                  StoredHash,
                  std::equal_to<const CallStack*>,
                  TableEntryAllocator> entry_map_;
diff --git a/components/metrics/leak_detector/call_stack_table_unittest.cc b/components/metrics/leak_detector/call_stack_table_unittest.cc
index f56f628..253b2bc 100644
--- a/components/metrics/leak_detector/call_stack_table_unittest.cc
+++ b/components/metrics/leak_detector/call_stack_table_unittest.cc
@@ -360,5 +360,99 @@
   EXPECT_EQ(stack2_, iter->value.call_stack());
 }
 
+TEST_F(CallStackTableTest, GetLastUptrendInfo) {
+  CallStackTable table(kDefaultLeakThreshold);
+
+  // Add some |stack0_| and |stack1_|.
+  for (int i = 0; i < 60; ++i)
+    table.Add(stack0_);
+  for (int i = 0; i < 60; ++i)
+    table.Add(stack1_);
+  table.UpdateLastDropInfo(100);
+
+  size_t timestamp_delta;
+  uint32_t count_delta;
+  table.GetLastUptrendInfo(stack0_, 100, &timestamp_delta, &count_delta);
+  EXPECT_EQ(0U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  // Remove |stack0_| and add |stack1_|.
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack0_);
+  for (int i = 0; i < 30; ++i)
+    table.Add(stack1_);
+  table.UpdateLastDropInfo(200);
+
+  table.GetLastUptrendInfo(stack0_, 200, &timestamp_delta, &count_delta);
+  EXPECT_EQ(0U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  table.GetLastUptrendInfo(stack1_, 200, &timestamp_delta, &count_delta);
+  EXPECT_EQ(100U, timestamp_delta);
+  EXPECT_EQ(30U, count_delta);
+
+  // Check if previous drop of |stack0_| was recorded and introduce |stack2_|.
+  for (int i = 0; i < 30; ++i)
+    table.Add(stack0_);
+  for (int i = 0; i < 60; ++i)
+    table.Add(stack2_);
+  table.UpdateLastDropInfo(300);
+
+  table.GetLastUptrendInfo(stack0_, 300, &timestamp_delta, &count_delta);
+  EXPECT_EQ(100U, timestamp_delta);
+  EXPECT_EQ(30U, count_delta);
+
+  table.GetLastUptrendInfo(stack2_, 300, &timestamp_delta, &count_delta);
+  EXPECT_EQ(0U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  // Introduce more variation between updates. Decrease |stack2_| to 0.
+  // All the history for |stack2_| should be forgotten.
+  for (int i = 0; i < 30; ++i)
+    table.Add(stack0_);
+  for (int i = 0; i < 40; ++i)
+    table.Remove(stack0_);
+  for (int i = 0; i < 40; ++i)
+    table.Add(stack1_);
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack1_);
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack2_);
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack2_);
+  table.UpdateLastDropInfo(400);
+
+  table.GetLastUptrendInfo(stack0_, 400, &timestamp_delta, &count_delta);
+  EXPECT_EQ(0U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  table.GetLastUptrendInfo(stack1_, 400, &timestamp_delta, &count_delta);
+  EXPECT_EQ(300U, timestamp_delta);
+  EXPECT_EQ(40U, count_delta);
+
+  table.GetLastUptrendInfo(stack2_, 400, &timestamp_delta, &count_delta);
+  EXPECT_EQ(400U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  // Make a 0-sum sequence for |stack0_|. Introduce |stack2_| again.
+  for (int i = 0; i < 30; ++i)
+    table.Add(stack0_);
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack0_);
+  for (int i = 0; i < 40; ++i)
+    table.Add(stack2_);
+  for (int i = 0; i < 30; ++i)
+    table.Remove(stack2_);
+  table.UpdateLastDropInfo(500);
+
+  table.GetLastUptrendInfo(stack0_, 500, &timestamp_delta, &count_delta);
+  EXPECT_EQ(100U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+
+  table.GetLastUptrendInfo(stack2_, 500, &timestamp_delta, &count_delta);
+  EXPECT_EQ(0U, timestamp_delta);
+  EXPECT_EQ(0U, count_delta);
+}
+
 }  // namespace leak_detector
 }  // namespace metrics
diff --git a/components/metrics/leak_detector/leak_detector.cc b/components/metrics/leak_detector/leak_detector.cc
index 5f33bab..6039e12 100644
--- a/components/metrics/leak_detector/leak_detector.cc
+++ b/components/metrics/leak_detector/leak_detector.cc
@@ -121,6 +121,9 @@
     proto_entry->set_count_for_call_stack(entry.count_for_call_stack);
   }
 
+  proto.set_num_rising_intervals(report.num_rising_intervals());
+  proto.set_num_allocs_increase(report.num_allocs_increase());
+
   return proto;
 }
 
@@ -272,8 +275,14 @@
       detector->last_analysis_alloc_size_ =
           total_alloc_size - total_alloc_size % analysis_interval_bytes;
 
+      // Collect new leak reports. Use current |total_alloc_size| as
+      // a timestamp, then transform change in timestamp into the
+      // number of intervals passed.
       InternalVector<InternalLeakReport> leak_reports;
-      detector->impl_->TestForLeaks(&leak_reports);
+      detector->impl_->TestForLeaks(&leak_reports, total_alloc_size);
+      for (auto &report : leak_reports)
+        report.set_num_rising_intervals(
+            report.num_rising_intervals() / analysis_interval_bytes);
 
       // Pass leak reports to observers.
       std::vector<MemoryLeakReportProto> leak_report_protos;
diff --git a/components/metrics/leak_detector/leak_detector.mojom b/components/metrics/leak_detector/leak_detector.mojom
index d985b79..08eea5119 100644
--- a/components/metrics/leak_detector/leak_detector.mojom
+++ b/components/metrics/leak_detector/leak_detector.mojom
@@ -20,6 +20,8 @@
 struct MemoryLeakReport {
   uint32 size_bytes;
   array<uint64> call_stack;
+  uint32 num_rising_intervals;
+  uint32 num_allocs_increase;
 
   array<AllocationBreakdown> alloc_breakdown_history;
 };
diff --git a/components/metrics/leak_detector/leak_detector_impl.cc b/components/metrics/leak_detector/leak_detector_impl.cc
index 5fd4653..43bd0d24 100644
--- a/components/metrics/leak_detector/leak_detector_impl.cc
+++ b/components/metrics/leak_detector/leak_detector_impl.cc
@@ -173,7 +173,8 @@
   address_map_.erase(iter);
 }
 
-void LeakDetectorImpl::TestForLeaks(InternalVector<LeakReport>* reports) {
+void LeakDetectorImpl::TestForLeaks(InternalVector<LeakReport>* reports,
+                                    size_t timestamp) {
   // Add net alloc counts for each size to a ranked list.
   RankedSet size_ranked_set(kRankedSetSize);
   for (size_t i = 0; i < size_entries_.size(); ++i) {
@@ -183,7 +184,7 @@
   }
   size_leak_analyzer_.AddSample(std::move(size_ranked_set));
 
-  RecordCurrentAllocationDataInHistory();
+  RecordCurrentAllocationDataInHistory(timestamp);
 
   UpdateLeakCooldowns();
 
@@ -230,7 +231,7 @@
         report->call_stack_[j] = GetOffset(call_stack->stack[j]);
       }
 
-      StoreHistoricalDataInReport(size, call_stack, report);
+      StoreHistoricalDataInReport(size, call_stack, report, timestamp);
       ResetLeakCooldown(size, call_stack);
     }
   }
@@ -253,7 +254,7 @@
   return UINTPTR_MAX;
 }
 
-void LeakDetectorImpl::RecordCurrentAllocationDataInHistory() {
+void LeakDetectorImpl::RecordCurrentAllocationDataInHistory(size_t timestamp) {
   // Record a snapshot of the current size table.
   InternalVector<uint32_t> current_size_table_record;
   current_size_table_record.reserve(kNumSizeEntriesInHistory);
@@ -273,6 +274,7 @@
       continue;
     RankedSet top_call_stacks(kNumTopCallStacksInHistory);
     entry.stack_table->GetTopCallStacks(&top_call_stacks);
+    entry.stack_table->UpdateLastDropInfo(timestamp);
     entry.call_site_breakdown_history.push_back(std::move(top_call_stacks));
     if (entry.call_site_breakdown_history.size() > kMaxNumHistoryEntries)
       entry.call_site_breakdown_history.pop_front();
@@ -281,7 +283,8 @@
 
 void LeakDetectorImpl::StoreHistoricalDataInReport(size_t size,
                                                    const CallStack* call_site,
-                                                   LeakReport* report) {
+                                                   LeakReport* report,
+                                                   size_t timestamp) {
   using AllocationBreakdown = LeakReport::AllocationBreakdown;
   // Copy historical allocation data into the report.
   InternalVector<AllocationBreakdown>* dest = &report->alloc_breakdown_history_;
@@ -317,6 +320,10 @@
     ++src_iter;
     ++dest_iter;
   }
+
+  size_entries_[SizeToIndex(size)].stack_table->GetLastUptrendInfo(
+      call_site, timestamp, &report->num_rising_intervals_,
+      &report->num_allocs_increase_);
 }
 
 bool LeakDetectorImpl::ReadyToGenerateReport(
diff --git a/components/metrics/leak_detector/leak_detector_impl.h b/components/metrics/leak_detector/leak_detector_impl.h
index bf6a58e..65c93d978 100644
--- a/components/metrics/leak_detector/leak_detector_impl.h
+++ b/components/metrics/leak_detector/leak_detector_impl.h
@@ -67,6 +67,14 @@
       return alloc_breakdown_history_;
     }
 
+    size_t num_rising_intervals() const { return num_rising_intervals_; }
+
+    uint32_t num_allocs_increase() const { return num_allocs_increase_; }
+
+    void set_num_rising_intervals(size_t num_rising_intervals) {
+      num_rising_intervals_ = num_rising_intervals;
+    }
+
     // Used to compare the contents of two leak reports.
     bool operator<(const LeakReport& other) const;
 
@@ -78,6 +86,12 @@
     // Number of bytes allocated by the leak site during each allocation.
     size_t alloc_size_bytes_;
 
+    // Number of intervals in the last uptrend.
+    size_t num_rising_intervals_;
+
+    // Net number of bytes allocated in the last uptrend.
+    uint32_t num_allocs_increase_;
+
     // Unlike the CallStack struct, which consists of addresses, this call stack
     // will contain offsets in the executable binary.
     InternalVector<uintptr_t> call_stack_;
@@ -105,7 +119,7 @@
   void RecordFree(const void* ptr);
 
   // Run check for possible leaks based on the current profiling data.
-  void TestForLeaks(InternalVector<LeakReport>* reports);
+  void TestForLeaks(InternalVector<LeakReport>* reports, size_t timestamp);
 
  private:
   // A record of allocations for a particular size.
@@ -160,17 +174,21 @@
   // per size is recorded in |size_breakdown_history_|. The net number of allocs
   // per call site for each size is recorded in
   // |AllocSizeEntry::call_site_breakdown_history|.
+  // Argument |timestamp| is used to update information about drops in
+  // allocation number for each stored call stack.
   //
   // Not all the net alloc counts are recorded. And the number of historical
   // records kept is capped. If adding a new record exceeds that limit, the
   // oldest record is discarded. See the function definition for more details.
-  void RecordCurrentAllocationDataInHistory();
+  void RecordCurrentAllocationDataInHistory(size_t timestamp);
 
   // Store the data collected by RecordCurrentAllocationDataInHistory() in
   // |*report|. Not all net alloc counts per call site will be stored, only the
-  // count for size=|size| and made from |call_site|.
+  // count for size=|size| and made from |call_site|. Also information
+  // about the last uptrend in net allocations for |size| and |call_site|
+  // is recorded with help of |timestamp|.
   void StoreHistoricalDataInReport(size_t size, const CallStack* call_site,
-                                   LeakReport* report);
+                                   LeakReport* report, size_t timestamp);
 
   // Decrements the cooldown counter (value) for each entry in
   // |cooldowns_per_leak_|. If the cooldown counter reaches 0, the entry is
diff --git a/components/metrics/leak_detector/leak_detector_impl_unittest.cc b/components/metrics/leak_detector/leak_detector_impl_unittest.cc
index f1e549b..28874f0b 100644
--- a/components/metrics/leak_detector/leak_detector_impl_unittest.cc
+++ b/components/metrics/leak_detector/leak_detector_impl_unittest.cc
@@ -184,7 +184,7 @@
     total_alloced_size_ += size;
     if (total_alloced_size_ >= next_analysis_total_alloced_size_) {
       InternalVector<InternalLeakReport> reports;
-      detector_->TestForLeaks(&reports);
+      detector_->TestForLeaks(&reports, 1024);
       for (const InternalLeakReport& report : reports) {
         auto iter = stored_reports_.find(report);
         if (iter == stored_reports_.end()) {
diff --git a/components/metrics/leak_detector/protobuf_to_mojo_converter.cc b/components/metrics/leak_detector/protobuf_to_mojo_converter.cc
index 24202dbc..8d18567 100644
--- a/components/metrics/leak_detector/protobuf_to_mojo_converter.cc
+++ b/components/metrics/leak_detector/protobuf_to_mojo_converter.cc
@@ -31,6 +31,8 @@
 void ReportToMojo(const MemoryLeakReportProto& report,
                   mojom::MemoryLeakReport* mojo_report) {
   mojo_report->size_bytes = report.size_bytes();
+  mojo_report->num_rising_intervals = report.num_rising_intervals();
+  mojo_report->num_allocs_increase = report.num_allocs_increase();
   for (auto call_stack_value : report.call_stack()) {
     mojo_report->call_stack.push_back(call_stack_value);
   }
@@ -50,6 +52,8 @@
 void MojoToReport(const mojom::MemoryLeakReport& mojo_report,
                   MemoryLeakReportProto* report) {
   report->set_size_bytes(mojo_report.size_bytes);
+  report->set_num_rising_intervals(mojo_report.num_rising_intervals);
+  report->set_num_allocs_increase(mojo_report.num_allocs_increase);
   for (auto call_stack_addr : mojo_report.call_stack)
     report->add_call_stack(call_stack_addr);
 
diff --git a/components/metrics/leak_detector/protobuf_to_mojo_converter_unittest.cc b/components/metrics/leak_detector/protobuf_to_mojo_converter_unittest.cc
index f768c31..2ba95a5 100644
--- a/components/metrics/leak_detector/protobuf_to_mojo_converter_unittest.cc
+++ b/components/metrics/leak_detector/protobuf_to_mojo_converter_unittest.cc
@@ -45,6 +45,8 @@
   report.add_call_stack(0xc001d00d);
   report.add_call_stack(0x900df00d);
   report.set_size_bytes(24);
+  report.set_num_rising_intervals(5);
+  report.set_num_allocs_increase(42);
 
   auto entry1 = report.add_alloc_breakdown_history();
   entry1->add_counts_by_size(1);
@@ -77,6 +79,8 @@
   EXPECT_EQ(0xc001d00d, mojo_report->call_stack[1]);
   EXPECT_EQ(0x900df00d, mojo_report->call_stack[2]);
   EXPECT_EQ(24U, mojo_report->size_bytes);
+  EXPECT_EQ(5U, mojo_report->num_rising_intervals);
+  EXPECT_EQ(42U, mojo_report->num_allocs_increase);
 
   ASSERT_EQ(3U, mojo_report->alloc_breakdown_history.size());
 
@@ -110,6 +114,8 @@
   EXPECT_EQ(0xc001d00d, new_report.call_stack(1));
   EXPECT_EQ(0x900df00d, new_report.call_stack(2));
   EXPECT_EQ(24U, new_report.size_bytes());
+  EXPECT_EQ(5U, new_report.num_rising_intervals());
+  EXPECT_EQ(42U, new_report.num_allocs_increase());
 
   ASSERT_EQ(3, new_report.alloc_breakdown_history().size());
 
diff --git a/components/metrics/proto/memory_leak_report.proto b/components/metrics/proto/memory_leak_report.proto
index 1a998fe..2e3d7b7 100644
--- a/components/metrics/proto/memory_leak_report.proto
+++ b/components/metrics/proto/memory_leak_report.proto
@@ -109,7 +109,9 @@
   //
   // |num_rising_intervals| equals timeslot_now - timeslot_drop,
   // where timeslot_drop is the timeslot number of the last frame that saw
-  // a drop in the number of allocations (or 0 if there were no drops).
+  // a drop in the number of allocations, or the first frame in the history
+  // if there were no drops (history is cleared when the net number of
+  // allocations hits 0).
   // If it is < 32, it will be visible in the allocation history graph.
   // If it is >= 32, it will not be seen in the graph.
   // E.g. for history [3,2,4,4,7] |num_rising_intervals| equals 3.
diff --git a/components/network_time/BUILD.gn b/components/network_time/BUILD.gn
index c7170ed..11e5c663 100644
--- a/components/network_time/BUILD.gn
+++ b/components/network_time/BUILD.gn
@@ -28,6 +28,7 @@
 
   deps = [
     ":network_time",
+    ":network_time_test_support",
     "//base",
     "//base/test:test_support",
     "//components/client_update_protocol",
@@ -38,3 +39,18 @@
     "//testing/gtest",
   ]
 }
+
+source_set("network_time_test_support") {
+  testonly = true
+  sources = [
+    "network_time_test_utils.cc",
+    "network_time_test_utils.h",
+  ]
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//components/variations",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/network_time/network_time_test_utils.cc b/components/network_time/network_time_test_utils.cc
new file mode 100644
index 0000000..63c6e53
--- /dev/null
+++ b/components/network_time/network_time_test_utils.cc
@@ -0,0 +1,63 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/network_time/network_time_test_utils.h"
+
+#include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/mock_entropy_provider.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace network_time {
+
+FieldTrialTest::FieldTrialTest() : ::testing::Test() {}
+FieldTrialTest::~FieldTrialTest() {}
+
+void FieldTrialTest::SetNetworkQueriesWithVariationsService(
+    bool enable,
+    float query_probability) {
+  const std::string kTrialName = "Trial";
+  const std::string kGroupName = "group";
+  const base::Feature kFeature{"NetworkTimeServiceQuerying",
+                               base::FEATURE_DISABLED_BY_DEFAULT};
+
+  // Clear all the things.
+  variations::testing::ClearAllVariationParams();
+
+  std::map<std::string, std::string> params;
+  params["RandomQueryProbability"] = base::DoubleToString(query_probability);
+  params["CheckTimeIntervalSeconds"] = base::Int64ToString(360);
+
+  // There are 3 things here: a FieldTrial, a FieldTrialList, and a
+  // FeatureList.  Don't get confused!  The FieldTrial is reference-counted,
+  // and a reference is held by the FieldTrialList.  The FieldTrialList and
+  // FeatureList are both singletons.  The authorized way to reset the former
+  // for testing is to destruct it (above).  The latter, by contrast, should
+  // should already start in a clean state and can be manipulated via the
+  // ScopedFeatureList helper class. If this comment was useful to you
+  // please send me a postcard.
+
+  field_trial_list_.reset();  // Averts a CHECK fail in constructor below.
+  field_trial_list_.reset(
+      new base::FieldTrialList(base::MakeUnique<base::MockEntropyProvider>()));
+  // refcounted, and reference held by field_trial_list_.
+  base::FieldTrial* trial = base::FieldTrialList::FactoryGetFieldTrial(
+      kTrialName, 100, kGroupName, 1971, 1, 1,
+      base::FieldTrial::SESSION_RANDOMIZED, nullptr /* default_group_number */);
+  ASSERT_TRUE(
+      variations::AssociateVariationParams(kTrialName, kGroupName, params));
+
+  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+  feature_list->RegisterFieldTrialOverride(
+      kFeature.name, enable ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
+                            : base::FeatureList::OVERRIDE_DISABLE_FEATURE,
+      trial);
+  scoped_feature_list_.reset(new base::test::ScopedFeatureList);
+  scoped_feature_list_->InitWithFeatureList(std::move(feature_list));
+}
+
+}  // namespace network_time
diff --git a/components/network_time/network_time_test_utils.h b/components/network_time/network_time_test_utils.h
new file mode 100644
index 0000000..09dee4c
--- /dev/null
+++ b/components/network_time/network_time_test_utils.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_NETWORK_TIME_NETWORK_TIME_TEST_UTILS_H_
+#define COMPONENTS_NETWORK_TIME_NETWORK_TIME_TEST_UTILS_H_
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace test {
+class ScopedFeatureList;
+};  // namespace test
+
+class FieldTrialList;
+}  // namespace base
+
+namespace network_time {
+// A test fixture that allows configuring the network time queries field trial.
+class FieldTrialTest : public ::testing::Test {
+ public:
+  FieldTrialTest();
+  ~FieldTrialTest() override;
+
+ protected:
+  void SetNetworkQueriesWithVariationsService(bool enable,
+                                              float query_probability);
+
+ private:
+  std::unique_ptr<base::FieldTrialList> field_trial_list_;
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(FieldTrialTest);
+};
+
+}  // namespace network_time
+
+#endif  // COMPONENTS_NETWORK_TIME_NETWORK_TIME_TEST_UTILS_H_
diff --git a/components/network_time/network_time_tracker_unittest.cc b/components/network_time/network_time_tracker_unittest.cc
index 312b4cd..408425ec 100644
--- a/components/network_time/network_time_tracker_unittest.cc
+++ b/components/network_time/network_time_tracker_unittest.cc
@@ -4,26 +4,20 @@
 
 #include "components/network_time/network_time_tracker.h"
 
-#include <map>
 #include <memory>
 #include <string>
 #include <utility>
 
 #include "base/compiler_specific.h"
-#include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
-#include "base/metrics/field_trial.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/histogram_tester.h"
-#include "base/test/mock_entropy_provider.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "components/client_update_protocol/ecdsa.h"
 #include "components/network_time/network_time_pref_names.h"
+#include "components/network_time/network_time_test_utils.h"
 #include "components/prefs/testing_pref_service.h"
-#include "components/variations/variations_associated_data.h"
 #include "net/http/http_response_headers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_response.h"
@@ -45,7 +39,7 @@
     "NetworkTimeTracker.WallClockRanBackwards";
 }  // namespace
 
-class NetworkTimeTrackerTest : public testing::Test {
+class NetworkTimeTrackerTest : public FieldTrialTest {
  public:
   ~NetworkTimeTrackerTest() override {}
 
@@ -180,50 +174,6 @@
   }
 
  protected:
-  void SetNetworkQueriesWithVariationsService(bool enable,
-                                              float query_probability) {
-    const std::string kTrialName = "Trial";
-    const std::string kGroupName = "group";
-    const base::Feature kFeature{"NetworkTimeServiceQuerying",
-                                 base::FEATURE_DISABLED_BY_DEFAULT};
-
-    // Clear all the things.
-    variations::testing::ClearAllVariationParams();
-
-    std::map<std::string, std::string> params;
-    params["RandomQueryProbability"] = base::DoubleToString(query_probability);
-    params["CheckTimeIntervalSeconds"] = base::Int64ToString(360);
-
-    // There are 3 things here: a FieldTrial, a FieldTrialList, and a
-    // FeatureList.  Don't get confused!  The FieldTrial is reference-counted,
-    // and a reference is held by the FieldTrialList.  The FieldTrialList and
-    // FeatureList are both singletons.  The authorized way to reset the former
-    // for testing is to destruct it (above).  The latter, by contrast, should
-    // should already start in a clean state and can be manipulated via the
-    // ScopedFeatureList helper class. If this comment was useful to you
-    // please send me a postcard.
-
-    field_trial_list_.reset();  // Averts a CHECK fail in constructor below.
-    field_trial_list_.reset(
-        new base::FieldTrialList(
-            base::MakeUnique<base::MockEntropyProvider>()));
-    // refcounted, and reference held by field_trial_list_.
-    base::FieldTrial* trial = base::FieldTrialList::FactoryGetFieldTrial(
-        kTrialName, 100, kGroupName, 1971, 1, 1,
-        base::FieldTrial::SESSION_RANDOMIZED,
-        nullptr /* default_group_number */);
-    ASSERT_TRUE(
-        variations::AssociateVariationParams(kTrialName, kGroupName, params));
-
-    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-    feature_list->RegisterFieldTrialOverride(
-        kFeature.name, enable ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
-                              : base::FeatureList::OVERRIDE_DISABLE_FEATURE,
-        trial);
-    scoped_feature_list_.reset(new base::test::ScopedFeatureList);
-    scoped_feature_list_->InitWithFeatureList(std::move(feature_list));
-  }
-
   base::Thread io_thread_;
   base::MessageLoop message_loop_;
   base::TimeDelta resolution_;
@@ -232,10 +182,8 @@
   base::SimpleTestClock* clock_;
   base::SimpleTestTickClock* tick_clock_;
   TestingPrefServiceSimple pref_service_;
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
   std::unique_ptr<NetworkTimeTracker> tracker_;
   std::unique_ptr<net::EmbeddedTestServer> test_server_;
-  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
 };
 
 TEST_F(NetworkTimeTrackerTest, Uninitialized) {
diff --git a/components/offline_pages/background/offliner_policy.h b/components/offline_pages/background/offliner_policy.h
index a82f5e68..d3424f39 100644
--- a/components/offline_pages/background/offliner_policy.h
+++ b/components/offline_pages/background/offliner_policy.h
@@ -8,7 +8,7 @@
 namespace {
 const int kMaxStartedTries = 4;
 const int kMaxCompletedTries = 1;
-const int kBackgroundProcessingTimeBudgetSeconds = 170;
+const int kDefaultBackgroundProcessingTimeBudgetSeconds = 170;
 const int kSinglePageTimeLimitWhenBackgroundScheduledSeconds = 120;
 const int kSinglePageTimeLimitForImmediateLoadSeconds = 300;
 const int kRequestExpirationTimeInSeconds = 60 * 60 * 24 * 7;
@@ -25,19 +25,23 @@
         prefer_earlier_requests_(true),
         retry_count_is_more_important_than_recency_(false),
         max_started_tries_(kMaxStartedTries),
-        max_completed_tries_(kMaxCompletedTries) {}
+        max_completed_tries_(kMaxCompletedTries),
+        background_processing_time_budget_(
+            kDefaultBackgroundProcessingTimeBudgetSeconds) {}
 
   // Constructor for unit tests.
   OfflinerPolicy(bool prefer_untried,
                  bool prefer_earlier,
                  bool prefer_retry_count,
                  int max_started_tries,
-                 int max_completed_tries)
+                 int max_completed_tries,
+                 int background_processing_time_budget)
       : prefer_untried_requests_(prefer_untried),
         prefer_earlier_requests_(prefer_earlier),
         retry_count_is_more_important_than_recency_(prefer_retry_count),
         max_started_tries_(max_started_tries),
-        max_completed_tries_(max_completed_tries) {}
+        max_completed_tries_(max_completed_tries),
+        background_processing_time_budget_(background_processing_time_budget) {}
 
   // TODO(petewil): Numbers here are chosen arbitrarily, do the proper studies
   // to get good policy numbers. Eventually this should get data from a finch
@@ -84,7 +88,7 @@
   // How many seconds to keep trying new pages for, before we give up,  and
   // return to the scheduler.
   int GetBackgroundProcessingTimeBudgetSeconds() const {
-    return kBackgroundProcessingTimeBudgetSeconds;
+    return background_processing_time_budget_;
   }
 
   // How long do we allow a page to load before giving up on it when
@@ -110,6 +114,7 @@
   bool retry_count_is_more_important_than_recency_;
   int max_started_tries_;
   int max_completed_tries_;
+  int background_processing_time_budget_;
 };
 }  // namespace offline_pages
 
diff --git a/components/offline_pages/background/request_coordinator.cc b/components/offline_pages/background/request_coordinator.cc
index 7d4eebf..81d90f1 100644
--- a/components/offline_pages/background/request_coordinator.cc
+++ b/components/offline_pages/background/request_coordinator.cc
@@ -136,6 +136,7 @@
       network_quality_estimator_(network_quality_estimator),
       active_request_(nullptr),
       last_offlining_status_(Offliner::RequestStatus::UNKNOWN),
+      immediate_schedule_callback_(base::Bind(&EmptySchedulerCallback)),
       weak_ptr_factory_(this) {
   DCHECK(policy_ != nullptr);
   picker_.reset(
@@ -464,10 +465,10 @@
   // Start processing with manufactured conservative battery conditions
   // (i.e., assume no battery).
   // TODO(dougarnett): Obtain actual battery conditions (from Android/Java).
+
   DeviceConditions device_conditions(false, 0, GetConnectionType());
   if (StartProcessingInternal(ProcessingWindowState::IMMEDIATE_WINDOW,
-                              device_conditions,
-                              base::Bind(&EmptySchedulerCallback)))
+                              device_conditions, immediate_schedule_callback_))
     return OfflinerImmediateStartStatus::STARTED;
   else
     return OfflinerImmediateStartStatus::NOT_ACCEPTED;
@@ -646,6 +647,8 @@
     case Offliner::RequestStatus::PRERENDERING_CANCELED:
     case Offliner::RequestStatus::PRERENDERING_FAILED:
       // No further processing in this service window.
+      // Let the scheduler know we are done processing.
+      scheduler_callback_.Run(true);
       break;
     default:
       // Make explicit choice about new status codes that actually reach here.
diff --git a/components/offline_pages/background/request_coordinator.h b/components/offline_pages/background/request_coordinator.h
index c523edb..371fd23 100644
--- a/components/offline_pages/background/request_coordinator.h
+++ b/components/offline_pages/background/request_coordinator.h
@@ -137,6 +137,14 @@
     scheduler_callback_ = callback;
   }
 
+  // A way to set the callback which would be called if the request will be
+  // scheduled immediately. Used by testing harness to determine if a request
+  // has been processed.
+  void SetImmediateScheduleCallbackForTest(
+      const base::Callback<void(bool)> callback) {
+    immediate_schedule_callback_ = callback;
+  }
+
   // Observers implementing the RequestCoordinator::Observer interface can
   // register here to get notifications of changes to request state.  This
   // pointer is not owned, and it is the callers responsibility to remove the
@@ -367,6 +375,8 @@
   RequestCoordinatorEventLogger event_logger_;
   // Timer to watch for pre-render attempts running too long.
   base::OneShotTimer watchdog_timer_;
+  // Callback invoked when an immediate request is done (default empty).
+  base::Callback<void(bool)> immediate_schedule_callback_;
   // Allows us to pass a weak pointer to callbacks.
   base::WeakPtrFactory<RequestCoordinator> weak_ptr_factory_;
 
diff --git a/components/offline_pages/background/request_coordinator_unittest.cc b/components/offline_pages/background/request_coordinator_unittest.cc
index 6a2c95d..52d442c 100644
--- a/components/offline_pages/background/request_coordinator_unittest.cc
+++ b/components/offline_pages/background/request_coordinator_unittest.cc
@@ -68,7 +68,6 @@
     conditions_ = trigger_conditions;
   }
 
-
   // Unschedules the currently scheduled task, if any.
   void Unschedule() override {
     unschedule_called_ = true;
@@ -239,7 +238,8 @@
   bool is_starting() { return coordinator_->is_starting(); }
 
   // Empty callback function.
-  void EmptyCallbackFunction(bool result) {
+  void ImmediateScheduleCallbackFunction(bool result) {
+    immediate_schedule_callback_called_ = true;
   }
 
   // Callback function which releases a wait for it.
@@ -326,6 +326,10 @@
 
   ObserverStub observer() { return observer_; }
 
+  bool immediate_schedule_callback_called() const {
+    return immediate_schedule_callback_called_;
+  }
+
  private:
   RequestQueue::GetRequestsResult last_get_requests_result_;
   MultipleItemStatuses last_remove_results_;
@@ -337,6 +341,7 @@
   OfflinerStub* offliner_;
   base::WaitableEvent waiter_;
   ObserverStub observer_;
+  bool immediate_schedule_callback_called_;
 };
 
 RequestCoordinatorTest::RequestCoordinatorTest()
@@ -345,7 +350,8 @@
       task_runner_handle_(task_runner_),
       offliner_(nullptr),
       waiter_(base::WaitableEvent::ResetPolicy::MANUAL,
-              base::WaitableEvent::InitialState::NOT_SIGNALED) {}
+              base::WaitableEvent::InitialState::NOT_SIGNALED),
+      immediate_schedule_callback_called_(false) {}
 
 RequestCoordinatorTest::~RequestCoordinatorTest() {}
 
@@ -407,10 +413,11 @@
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
+  PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 }
 
 TEST_F(RequestCoordinatorTest, StartProcessingWithRequestInProgress) {
@@ -424,7 +431,7 @@
   DeviceConditions device_conditions(
       false, 75, net::NetworkChangeNotifier::CONNECTION_3G);
   base::Callback<void(bool)> callback =
-      base::Bind(&RequestCoordinatorTest::EmptyCallbackFunction,
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
                  base::Unretained(this));
 
   // Ensure that the forthcoming request does not finish - we simulate it being
@@ -435,24 +442,88 @@
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
   PumpLoop();
   EXPECT_TRUE(is_busy());
+  // Since the offliner is disabled, this callback should not be called.
+  EXPECT_FALSE(immediate_schedule_callback_called());
 
   // Now trying to start processing on another request should return false.
   EXPECT_FALSE(coordinator()->StartProcessing(device_conditions, callback));
 }
 
 TEST_F(RequestCoordinatorTest, SavePageLater) {
+  // Set up device conditions for the test and enable the offliner.
+  DeviceConditions device_conditions(false, 75,
+                                     net::NetworkChangeNotifier::CONNECTION_3G);
+  SetDeviceConditionsForTest(device_conditions);
+  SetEffectiveConnectionTypeForTest(
+      net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_3G);
+  EnableOfflinerCallback(true);
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
+
+  // The user-requested request which gets processed by SavePageLater
+  // would invoke user request callback.
+  coordinator()->SetImmediateScheduleCallbackForTest(callback);
+
   EXPECT_NE(
       coordinator()->SavePageLater(
           kUrl1, kClientId1, kUserRequested,
           RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER), 0);
 
   // Expect that a request got placed on the queue.
+  coordinator()->queue()->GetRequests(base::Bind(
+      &RequestCoordinatorTest::GetRequestsDone, base::Unretained(this)));
+
+  // Wait for callbacks to finish, both request queue and offliner.
+  PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
+
+  // Check the request queue is as expected.
+  EXPECT_EQ(1UL, last_requests().size());
+  EXPECT_EQ(kUrl1, last_requests().at(0)->url());
+  EXPECT_EQ(kClientId1, last_requests().at(0)->client_id());
+
+  // Expect that the scheduler got notified.
+  SchedulerStub* scheduler_stub =
+      reinterpret_cast<SchedulerStub*>(coordinator()->scheduler());
+  EXPECT_TRUE(scheduler_stub->schedule_called());
+  EXPECT_EQ(coordinator()
+                ->GetTriggerConditions(last_requests()[0]->user_requested())
+                .minimum_battery_percentage,
+            scheduler_stub->conditions()->minimum_battery_percentage);
+
+  // Check that the observer got the notification that a page is available
+  EXPECT_TRUE(observer().added_called());
+}
+
+TEST_F(RequestCoordinatorTest, SavePageLaterFailed) {
+  // Set up device conditions for the test and enable the offliner.
+  DeviceConditions device_conditions(false, 75,
+                                     net::NetworkChangeNotifier::CONNECTION_3G);
+  SetDeviceConditionsForTest(device_conditions);
+  SetEffectiveConnectionTypeForTest(
+      net::EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_3G);
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
+  // The user-requested request which gets processed by SavePageLater
+  // would invoke user request callback.
+  coordinator()->SetImmediateScheduleCallbackForTest(callback);
+
+  EXPECT_TRUE(
+      coordinator()->SavePageLater(
+          kUrl1, kClientId1, kUserRequested,
+          RequestCoordinator::RequestAvailability::ENABLED_FOR_OFFLINER) != 0);
+
+  // Expect that a request got placed on the queue.
   coordinator()->queue()->GetRequests(
       base::Bind(&RequestCoordinatorTest::GetRequestsDone,
                  base::Unretained(this)));
 
   // Wait for callbacks to finish, both request queue and offliner.
   PumpLoop();
+  // Will not be called since the offliner is disabled.
+  EXPECT_FALSE(immediate_schedule_callback_called());
 
   // Check the request queue is as expected.
   EXPECT_EQ(1UL, last_requests().size());
@@ -485,9 +556,8 @@
 
   // We need to give a callback to the request.
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
   // Set up device conditions for the test.
@@ -499,6 +569,7 @@
   // for callbacks.
   SendOfflinerDoneCallback(request, Offliner::RequestStatus::SAVED);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // Verify the request gets removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(
@@ -539,9 +610,8 @@
 
   // We need to give a callback to the request.
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
   // Set up device conditions for the test.
@@ -554,6 +624,7 @@
   SendOfflinerDoneCallback(request,
                            Offliner::RequestStatus::PRERENDERING_FAILED);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // TODO(dougarnett): Consider injecting mock RequestPicker for this test
   // and verifying that there is no attempt to pick another request following
@@ -593,8 +664,9 @@
   PumpLoop();
 
   // We need to give a callback to the request.
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
   // Set up device conditions for the test.
@@ -607,6 +679,7 @@
   SendOfflinerDoneCallback(
       request, Offliner::RequestStatus::PRERENDERING_FAILED_NO_RETRY);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // TODO(dougarnett): Consider injecting mock RequestPicker for this test
   // and verifying that there is as attempt to pick another request following
@@ -636,8 +709,9 @@
   PumpLoop();
 
   // We need to give a callback to the request.
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
   // Set up device conditions for the test.
@@ -650,6 +724,7 @@
   SendOfflinerDoneCallback(request,
                            Offliner::RequestStatus::FOREGROUND_CANCELED);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // Verify the request is not removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(base::Bind(
@@ -673,8 +748,9 @@
   PumpLoop();
 
   // We need to give a callback to the request.
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->SetProcessingCallbackForTest(callback);
 
   // Set up device conditions for the test.
@@ -687,6 +763,7 @@
   SendOfflinerDoneCallback(request,
                            Offliner::RequestStatus::PRERENDERING_CANCELED);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // Verify the request is not removed from the queue, and wait for callbacks.
   coordinator()->queue()->GetRequests(base::Bind(
@@ -707,8 +784,9 @@
   // Call start processing just to set up a scheduler callback.
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->StartProcessing(device_conditions, callback);
   EXPECT_TRUE(is_starting());
 
@@ -732,8 +810,9 @@
   // Call start processing just to set up a scheduler callback.
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   coordinator()->StartProcessing(device_conditions, callback);
   EXPECT_TRUE(is_starting());
 
@@ -743,6 +822,7 @@
   PumpLoop();
 
   EXPECT_FALSE(is_starting());
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // The scheduler should have been called to schedule the non-user requested
   // task.
@@ -807,13 +887,13 @@
                                      net::NetworkChangeNotifier::CONNECTION_3G);
   DisableLoading();
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
 
   // Let the async callbacks in the request coordinator run.
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   EXPECT_FALSE(is_starting());
   EXPECT_EQ(Offliner::PRERENDERING_NOT_STARTED, last_offlining_status());
@@ -834,9 +914,8 @@
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
   EXPECT_TRUE(is_starting());
 
@@ -845,6 +924,7 @@
 
   // Let the async callbacks in the request coordinator run.
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   EXPECT_FALSE(is_starting());
 
@@ -874,14 +954,15 @@
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
   base::Callback<void(bool)> callback =
-      base::Bind(
-          &RequestCoordinatorTest::EmptyCallbackFunction,
-          base::Unretained(this));
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
   EXPECT_TRUE(is_starting());
 
   // Let all the async parts of the start processing pipeline run to completion.
   PumpLoop();
+  // Since the offliner is disabled, this callback should not be called.
+  EXPECT_FALSE(immediate_schedule_callback_called());
 
   // Coordinator should now be busy.
   EXPECT_TRUE(is_busy());
@@ -920,12 +1001,15 @@
 
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
 
   // Let all the async parts of the start processing pipeline run to completion.
   PumpLoop();
+  // Since the offliner is disabled, this callback should not be called.
+  EXPECT_FALSE(immediate_schedule_callback_called());
 
   // Remove the request while it is processing.
   std::vector<int64_t> request_ids{kRequestId1};
@@ -955,13 +1039,15 @@
 
   DeviceConditions device_conditions(false, 75,
                                      net::NetworkChangeNotifier::CONNECTION_3G);
-  base::Callback<void(bool)> callback = base::Bind(
-      &RequestCoordinatorTest::EmptyCallbackFunction, base::Unretained(this));
+  base::Callback<void(bool)> callback =
+      base::Bind(&RequestCoordinatorTest::ImmediateScheduleCallbackFunction,
+                 base::Unretained(this));
   EXPECT_TRUE(coordinator()->StartProcessing(device_conditions, callback));
 
   // Call the method under test, making sure we send SUCCESS to the observer.
   coordinator()->MarkRequestCompleted(request_id);
   PumpLoop();
+  EXPECT_TRUE(immediate_schedule_callback_called());
 
   // Our observer should have seen SUCCESS instead of REMOVED.
   EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::SUCCESS,
diff --git a/components/offline_pages/background/request_picker_unittest.cc b/components/offline_pages/background/request_picker_unittest.cc
index f71b4064..963f9aa 100644
--- a/components/offline_pages/background/request_picker_unittest.cc
+++ b/components/offline_pages/background/request_picker_unittest.cc
@@ -38,6 +38,7 @@
 const bool kPreferUntried = false;
 const bool kPreferEarlier = true;
 const bool kPreferRetryCount = true;
+const int kBackgroundProcessingTimeBudgetSeconds = 170;
 
 // Default request
 const SavePageRequest kEmptyRequest(0UL,
@@ -194,9 +195,9 @@
 }
 
 TEST_F(RequestPickerTest, ChooseRequestWithHigherRetryCount) {
-  policy_.reset(new OfflinerPolicy(kPreferUntried, kPreferEarlier,
-                                   kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries + 1));
+  policy_.reset(new OfflinerPolicy(
+      kPreferUntried, kPreferEarlier, kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries + 1, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
@@ -230,9 +231,9 @@
 
 TEST_F(RequestPickerTest, ChooseEarlierRequest) {
   // We need a custom policy object prefering recency to retry count.
-  policy_.reset(new OfflinerPolicy(kPreferUntried, kPreferEarlier,
-                                   !kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries));
+  policy_.reset(new OfflinerPolicy(
+      kPreferUntried, kPreferEarlier, !kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
@@ -253,9 +254,9 @@
 
 TEST_F(RequestPickerTest, ChooseSameTimeRequestWithHigherRetryCount) {
   // We need a custom policy object preferring recency to retry count.
-  policy_.reset(new OfflinerPolicy(kPreferUntried, kPreferEarlier,
-                                   !kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries + 1));
+  policy_.reset(new OfflinerPolicy(
+      kPreferUntried, kPreferEarlier, !kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries + 1, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
@@ -274,9 +275,9 @@
 
 TEST_F(RequestPickerTest, ChooseRequestWithLowerRetryCount) {
   // We need a custom policy object preferring lower retry count.
-  policy_.reset(new OfflinerPolicy(!kPreferUntried, kPreferEarlier,
-                                   kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries + 1));
+  policy_.reset(new OfflinerPolicy(
+      !kPreferUntried, kPreferEarlier, kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries + 1, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
@@ -295,9 +296,9 @@
 
 TEST_F(RequestPickerTest, ChooseLaterRequest) {
   // We need a custom policy preferring recency over retry, and later requests.
-  policy_.reset(new OfflinerPolicy(kPreferUntried, !kPreferEarlier,
-                                   !kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries));
+  policy_.reset(new OfflinerPolicy(
+      kPreferUntried, !kPreferEarlier, !kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
@@ -377,9 +378,9 @@
 
 
 TEST_F(RequestPickerTest, ChooseRequestThatIsNotDisabled) {
-  policy_.reset(new OfflinerPolicy(kPreferUntried, kPreferEarlier,
-                                   kPreferRetryCount, kMaxStartedTries,
-                                   kMaxCompletedTries + 1));
+  policy_.reset(new OfflinerPolicy(
+      kPreferUntried, kPreferEarlier, kPreferRetryCount, kMaxStartedTries,
+      kMaxCompletedTries + 1, kBackgroundProcessingTimeBudgetSeconds));
   picker_.reset(new RequestPicker(queue_.get(), policy_.get(), notifier_.get(),
                                   &event_logger_));
 
diff --git a/components/proximity_auth/connection.cc b/components/proximity_auth/connection.cc
index 5b61e06..3c7ff25 100644
--- a/components/proximity_auth/connection.cc
+++ b/components/proximity_auth/connection.cc
@@ -60,8 +60,8 @@
 
   Status old_status = status_;
   status_ = status;
-  FOR_EACH_OBSERVER(ConnectionObserver, observers_,
-                    OnConnectionStatusChanged(this, old_status, status_));
+  for (auto& observer : observers_)
+    observer.OnConnectionStatusChanged(this, old_status, status_);
 }
 
 void Connection::OnDidSendMessage(const WireMessage& message, bool success) {
@@ -71,8 +71,8 @@
   }
 
   is_sending_message_ = false;
-  FOR_EACH_OBSERVER(
-      ConnectionObserver, observers_, OnSendCompleted(*this, message, success));
+  for (auto& observer : observers_)
+    observer.OnSendCompleted(*this, message, success);
 }
 
 void Connection::OnBytesReceived(const std::string& bytes) {
@@ -90,8 +90,8 @@
     return;
 
   if (message) {
-    FOR_EACH_OBSERVER(
-        ConnectionObserver, observers_, OnMessageReceived(*this, *message));
+    for (auto& observer : observers_)
+      observer.OnMessageReceived(*this, *message);
   }
 
   // Whether the message was parsed successfully or not, clear the
diff --git a/components/proximity_auth/cryptauth/cryptauth_device_manager.cc b/components/proximity_auth/cryptauth/cryptauth_device_manager.cc
index c74d523..2d288c0 100644
--- a/components/proximity_auth/cryptauth/cryptauth_device_manager.cc
+++ b/components/proximity_auth/cryptauth/cryptauth_device_manager.cc
@@ -215,11 +215,12 @@
   sync_request_->OnDidComplete(true);
   cryptauth_client_.reset();
   sync_request_.reset();
-  FOR_EACH_OBSERVER(
-      Observer, observers_,
-      OnSyncFinished(SyncResult::SUCCESS, unlock_keys_changed
-                                              ? DeviceChangeResult::CHANGED
-                                              : DeviceChangeResult::UNCHANGED));
+  for (auto& observer : observers_) {
+    observer.OnSyncFinished(SyncResult::SUCCESS,
+                            unlock_keys_changed
+                                ? DeviceChangeResult::CHANGED
+                                : DeviceChangeResult::UNCHANGED);
+  }
 }
 
 void CryptAuthDeviceManager::OnGetMyDevicesFailure(const std::string& error) {
@@ -229,9 +230,8 @@
   sync_request_->OnDidComplete(false);
   cryptauth_client_.reset();
   sync_request_.reset();
-  FOR_EACH_OBSERVER(
-      Observer, observers_,
-      OnSyncFinished(SyncResult::FAILURE, DeviceChangeResult::UNCHANGED));
+  for (auto& observer : observers_)
+    observer.OnSyncFinished(SyncResult::FAILURE, DeviceChangeResult::UNCHANGED);
 }
 
 std::unique_ptr<SyncScheduler> CryptAuthDeviceManager::CreateSyncScheduler() {
@@ -268,7 +268,8 @@
 
 void CryptAuthDeviceManager::OnSyncRequested(
     std::unique_ptr<SyncScheduler::SyncRequest> sync_request) {
-  FOR_EACH_OBSERVER(Observer, observers_, OnSyncStarted());
+  for (auto& observer : observers_)
+    observer.OnSyncStarted();
 
   sync_request_ = std::move(sync_request);
   cryptauth_client_ = client_factory_->CreateInstance();
diff --git a/components/proximity_auth/cryptauth/cryptauth_enrollment_manager.cc b/components/proximity_auth/cryptauth/cryptauth_enrollment_manager.cc
index c2f8093c..ca65b47 100644
--- a/components/proximity_auth/cryptauth/cryptauth_enrollment_manager.cc
+++ b/components/proximity_auth/cryptauth/cryptauth_enrollment_manager.cc
@@ -155,7 +155,8 @@
   sync_request_->OnDidComplete(success);
   cryptauth_enroller_.reset();
   sync_request_.reset();
-  FOR_EACH_OBSERVER(Observer, observers_, OnEnrollmentFinished(success));
+  for (auto& observer : observers_)
+    observer.OnEnrollmentFinished(success);
 }
 
 std::unique_ptr<SyncScheduler>
@@ -230,7 +231,8 @@
 
 void CryptAuthEnrollmentManager::OnSyncRequested(
     std::unique_ptr<SyncScheduler::SyncRequest> sync_request) {
-  FOR_EACH_OBSERVER(Observer, observers_, OnEnrollmentStarted());
+  for (auto& observer : observers_)
+    observer.OnEnrollmentStarted();
 
   sync_request_ = std::move(sync_request);
   if (gcm_manager_->GetRegistrationId().empty() ||
diff --git a/components/proximity_auth/cryptauth/cryptauth_gcm_manager_impl.cc b/components/proximity_auth/cryptauth/cryptauth_gcm_manager_impl.cc
index 588ddcf..77680e8f 100644
--- a/components/proximity_auth/cryptauth/cryptauth_gcm_manager_impl.cc
+++ b/components/proximity_auth/cryptauth/cryptauth_gcm_manager_impl.cc
@@ -102,9 +102,11 @@
     if (tickle_type == kRegistrationTickleTypeForceEnrollment ||
         tickle_type == kRegistrationTickleTypeUpdateEnrollment) {
       // These tickle types correspond to re-enrollment messages.
-      FOR_EACH_OBSERVER(Observer, observers_, OnReenrollMessage());
+      for (auto& observer : observers_)
+        observer.OnReenrollMessage();
     } else if (tickle_type == kRegistrationTickleTypeDevicesSync) {
-      FOR_EACH_OBSERVER(Observer, observers_, OnResyncMessage());
+      for (auto& observer : observers_)
+        observer.OnResyncMessage();
     } else {
       PA_LOG(WARNING) << "Unknown tickle type in GCM message.";
     }
@@ -133,14 +135,16 @@
   if (result != gcm::GCMClient::SUCCESS) {
     PA_LOG(WARNING) << "GCM registration failed with result="
                     << static_cast<int>(result);
-    FOR_EACH_OBSERVER(Observer, observers_, OnGCMRegistrationResult(false));
+    for (auto& observer : observers_)
+      observer.OnGCMRegistrationResult(false);
     return;
   }
 
   PA_LOG(INFO) << "GCM registration success, registration_id="
                << registration_id;
   pref_service_->SetString(prefs::kCryptAuthGCMRegistrationId, registration_id);
-  FOR_EACH_OBSERVER(Observer, observers_, OnGCMRegistrationResult(true));
+  for (auto& observer : observers_)
+    observer.OnGCMRegistrationResult(true);
 }
 
 }  // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/fake_cryptauth_gcm_manager.cc b/components/proximity_auth/cryptauth/fake_cryptauth_gcm_manager.cc
index 806e1be..d1e85c6 100644
--- a/components/proximity_auth/cryptauth/fake_cryptauth_gcm_manager.cc
+++ b/components/proximity_auth/cryptauth/fake_cryptauth_gcm_manager.cc
@@ -36,15 +36,18 @@
   registration_in_progress_ = false;
   registration_id_ = registration_id;
   bool success = !registration_id_.empty();
-  FOR_EACH_OBSERVER(Observer, observers_, OnGCMRegistrationResult(success));
+  for (auto& observer : observers_)
+    observer.OnGCMRegistrationResult(success);
 }
 
 void FakeCryptAuthGCMManager::PushReenrollMessage() {
-  FOR_EACH_OBSERVER(Observer, observers_, OnReenrollMessage());
+  for (auto& observer : observers_)
+    observer.OnReenrollMessage();
 }
 
 void FakeCryptAuthGCMManager::PushResyncMessage() {
-  FOR_EACH_OBSERVER(Observer, observers_, OnResyncMessage());
+  for (auto& observer : observers_)
+    observer.OnResyncMessage();
 }
 
 }  // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/mock_cryptauth_client.cc b/components/proximity_auth/cryptauth/mock_cryptauth_client.cc
index 51dd4eb8..4480623 100644
--- a/components/proximity_auth/cryptauth/mock_cryptauth_client.cc
+++ b/components/proximity_auth/cryptauth/mock_cryptauth_client.cc
@@ -30,8 +30,8 @@
   else
     client.reset(new testing::NiceMock<MockCryptAuthClient>());
 
-  FOR_EACH_OBSERVER(Observer, observer_list_,
-                    OnCryptAuthClientCreated(client.get()));
+  for (auto& observer : observer_list_)
+    observer.OnCryptAuthClientCreated(client.get());
   return std::move(client);
 }
 
diff --git a/components/proximity_auth/logging/log_buffer.cc b/components/proximity_auth/logging/log_buffer.cc
index 5e23d220..8f53153 100644
--- a/components/proximity_auth/logging/log_buffer.cc
+++ b/components/proximity_auth/logging/log_buffer.cc
@@ -51,12 +51,14 @@
   log_messages_.push_back(log_message);
   if (log_messages_.size() > MaxBufferSize())
     log_messages_.pop_front();
-  FOR_EACH_OBSERVER(Observer, observers_, OnLogMessageAdded(log_message));
+  for (auto& observer : observers_)
+    observer.OnLogMessageAdded(log_message);
 }
 
 void LogBuffer::Clear() {
   log_messages_.clear();
-  FOR_EACH_OBSERVER(Observer, observers_, OnLogBufferCleared());
+  for (auto& observer : observers_)
+    observer.OnLogBufferCleared();
 }
 
 size_t LogBuffer::MaxBufferSize() const {
diff --git a/components/proximity_auth/messenger_impl.cc b/components/proximity_auth/messenger_impl.cc
index 1a0d027..05169bf 100644
--- a/components/proximity_auth/messenger_impl.cc
+++ b/components/proximity_auth/messenger_impl.cc
@@ -115,8 +115,8 @@
   if (!SupportsSignIn()) {
     PA_LOG(WARNING) << "Dropping decryption request, as remote device "
                     << "does not support protocol v3.1.";
-    FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                      OnDecryptResponse(std::string()));
+    for (auto& observer : observers_)
+      observer.OnDecryptResponse(std::string());
     return;
   }
 
@@ -137,7 +137,8 @@
   if (!SupportsSignIn()) {
     PA_LOG(WARNING) << "Dropping unlock request, as remote device does not "
                     << "support protocol v3.1.";
-    FOR_EACH_OBSERVER(MessengerObserver, observers_, OnUnlockResponse(false));
+    for (auto& observer : observers_)
+      observer.OnUnlockResponse(false);
     return;
   }
 
@@ -189,8 +190,8 @@
         (decoded_message == kScreenUnlocked ? USER_PRESENT : USER_ABSENT);
     update.secure_screen_lock_state = SECURE_SCREEN_LOCK_ENABLED;
     update.trust_agent_state = TRUST_AGENT_ENABLED;
-    FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                      OnRemoteStatusUpdate(update));
+    for (auto& observer : observers_)
+      observer.OnRemoteStatusUpdate(update);
     pending_message_.reset();
     ProcessMessageQueue();
     return;
@@ -263,8 +264,8 @@
     return;
   }
 
-  FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                    OnRemoteStatusUpdate(*status_update));
+  for (auto& observer : observers_)
+    observer.OnRemoteStatusUpdate(*status_update);
 }
 
 void MessengerImpl::HandleDecryptResponseMessage(
@@ -279,13 +280,14 @@
     PA_LOG(ERROR) << "Unable to base64-decode decrypt response.";
   }
 
-  FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                    OnDecryptResponse(decrypted_data));
+  for (auto& observer : observers_)
+    observer.OnDecryptResponse(decrypted_data);
 }
 
 void MessengerImpl::HandleUnlockResponseMessage(
     const base::DictionaryValue& message) {
-  FOR_EACH_OBSERVER(MessengerObserver, observers_, OnUnlockResponse(true));
+  for (auto& observer : observers_)
+    observer.OnUnlockResponse(true);
 }
 
 void MessengerImpl::PollScreenStateForIOS() {
@@ -310,7 +312,8 @@
   if (new_status == Connection::DISCONNECTED) {
     PA_LOG(INFO) << "Secure channel disconnected...";
     connection_->RemoveObserver(this);
-    FOR_EACH_OBSERVER(MessengerObserver, observers_, OnDisconnected());
+    for (auto& observer : observers_)
+      observer.OnDisconnected();
     // TODO(isherman): Determine whether it's also necessary/appropriate to fire
     // this notification from the destructor.
   }
@@ -342,13 +345,14 @@
   // For local events, we don't expect a response, so on success, we
   // notify observers right away.
   if (pending_message_->type == kMessageTypeDecryptRequest) {
-    FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                      OnDecryptResponse(std::string()));
+    for (auto& observer : observers_)
+      observer.OnDecryptResponse(std::string());
   } else if (pending_message_->type == kMessageTypeUnlockRequest) {
-    FOR_EACH_OBSERVER(MessengerObserver, observers_, OnUnlockResponse(false));
+    for (auto& observer : observers_)
+      observer.OnUnlockResponse(false);
   } else if (pending_message_->type == kMessageTypeLocalEvent) {
-    FOR_EACH_OBSERVER(MessengerObserver, observers_,
-                      OnUnlockEventSent(success));
+    for (auto& observer : observers_)
+      observer.OnUnlockEventSent(success);
   } else {
     PA_LOG(ERROR) << "Message of unknown type '" << pending_message_->type
                   << "' sent.";
diff --git a/components/proximity_auth/proximity_monitor_impl.cc b/components/proximity_auth/proximity_monitor_impl.cc
index 793062b..cf908b4 100644
--- a/components/proximity_auth/proximity_monitor_impl.cc
+++ b/components/proximity_auth/proximity_monitor_impl.cc
@@ -221,8 +221,8 @@
 
 void ProximityMonitorImpl::ClearProximityState() {
   if (is_active_ && remote_device_is_in_proximity_) {
-    FOR_EACH_OBSERVER(ProximityMonitorObserver, observers_,
-                      OnProximityStateChanged());
+    for (auto& observer : observers_)
+      observer.OnProximityStateChanged();
   }
 
   remote_device_is_in_proximity_ = false;
@@ -273,8 +273,8 @@
     PA_LOG(INFO) << "[Proximity] Updated proximity state: "
                  << (is_now_in_proximity ? "proximate" : "distant");
     remote_device_is_in_proximity_ = is_now_in_proximity;
-    FOR_EACH_OBSERVER(ProximityMonitorObserver, observers_,
-                      OnProximityStateChanged());
+    for (auto& observer : observers_)
+      observer.OnProximityStateChanged();
   }
 }
 
diff --git a/components/proximity_auth/remote_device_life_cycle_impl.cc b/components/proximity_auth/remote_device_life_cycle_impl.cc
index a5c37be..0095e59 100644
--- a/components/proximity_auth/remote_device_life_cycle_impl.cc
+++ b/components/proximity_auth/remote_device_life_cycle_impl.cc
@@ -107,8 +107,8 @@
                << " => " << static_cast<int>(new_state);
   RemoteDeviceLifeCycle::State old_state = state_;
   state_ = new_state;
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnLifeCycleStateChanged(old_state, new_state));
+  for (auto& observer : observers_)
+    observer.OnLifeCycleStateChanged(old_state, new_state);
 }
 
 void RemoteDeviceLifeCycleImpl::FindConnection() {
diff --git a/components/proximity_auth/screenlock_bridge.cc b/components/proximity_auth/screenlock_bridge.cc
index 3a941148..d3996b1 100644
--- a/components/proximity_auth/screenlock_bridge.cc
+++ b/components/proximity_auth/screenlock_bridge.cc
@@ -134,10 +134,13 @@
 
   focused_account_id_ = EmptyAccountId();
   lock_handler_ = lock_handler;
-  if (lock_handler_)
-    FOR_EACH_OBSERVER(Observer, observers_, OnScreenDidLock(screen_type));
-  else
-    FOR_EACH_OBSERVER(Observer, observers_, OnScreenDidUnlock(screen_type));
+  if (lock_handler_) {
+    for (auto& observer : observers_)
+      observer.OnScreenDidLock(screen_type);
+  } else {
+    for (auto& observer : observers_)
+      observer.OnScreenDidUnlock(screen_type);
+  }
 }
 
 void ScreenlockBridge::SetFocusedUser(const AccountId& account_id) {
@@ -145,7 +148,8 @@
     return;
   PA_LOG(INFO) << "Focused user changed to " << account_id.Serialize();
   focused_account_id_ = account_id;
-  FOR_EACH_OBSERVER(Observer, observers_, OnFocusedUserChanged(account_id));
+  for (auto& observer : observers_)
+    observer.OnFocusedUserChanged(account_id);
 }
 
 bool ScreenlockBridge::IsLocked() const {
diff --git a/components/renderer_context_menu/render_view_context_menu_base.cc b/components/renderer_context_menu/render_view_context_menu_base.cc
index b743f9e..63d8df50 100644
--- a/components/renderer_context_menu/render_view_context_menu_base.cc
+++ b/components/renderer_context_menu/render_view_context_menu_base.cc
@@ -351,9 +351,8 @@
   source_web_contents_->NotifyContextMenuClosed(params_.custom_context);
 
   if (!command_executed_) {
-    FOR_EACH_OBSERVER(RenderViewContextMenuObserver,
-                      observers_,
-                      OnMenuCancel());
+    for (auto& observer : observers_)
+      observer.OnMenuCancel();
   }
 }
 
diff --git a/components/safe_browsing_db/v4_database.cc b/components/safe_browsing_db/v4_database.cc
index 30a3b0f5..4b43498 100644
--- a/components/safe_browsing_db/v4_database.cc
+++ b/components/safe_browsing_db/v4_database.cc
@@ -142,7 +142,9 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(pending_store_updates_);
   if (new_store) {
-    (*store_map_)[identifier] = std::move(new_store);
+    (*store_map_)[identifier].swap(new_store);
+    // |new_store| now is the store that needs to be destroyed on task runner.
+    V4Store::Destroy(std::move(new_store));
   }
 
   pending_store_updates_--;
diff --git a/components/safe_browsing_db/v4_database_unittest.cc b/components/safe_browsing_db/v4_database_unittest.cc
index 057602a..a72e6a8 100644
--- a/components/safe_browsing_db/v4_database_unittest.cc
+++ b/components/safe_browsing_db/v4_database_unittest.cc
@@ -187,6 +187,11 @@
     verify_checksum_called_back_ = true;
   }
 
+  void WaitForTasksOnTaskRunner() {
+    task_runner_->RunPendingTasks();
+    base::RunLoop().RunUntilIdle();
+  }
+
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
   std::unique_ptr<V4Database> v4_database_;
   base::FilePath database_dirname_;
@@ -213,9 +218,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 }
 
@@ -226,8 +229,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   // The database has now been created. Time to try to update it.
   EXPECT_TRUE(v4_database_);
@@ -243,10 +245,13 @@
       CreateFakeServerResponse(expected_store_state_map_, true),
       callback_db_updated_);
 
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  // Wait for the ApplyUpdate callback to get called.
+  WaitForTasksOnTaskRunner();
 
   VerifyExpectedStoresState(true);
+
+  // Wait for the old stores to get destroyed on task runner.
+  WaitForTasksOnTaskRunner();
 }
 
 // Test to ensure no state updates leads to no store updates.
@@ -256,8 +261,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   // The database has now been created. Time to try to update it.
   EXPECT_TRUE(v4_database_);
@@ -273,8 +277,7 @@
       CreateFakeServerResponse(expected_store_state_map_, true),
       callback_db_updated_);
 
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   VerifyExpectedStoresState(false);
 }
@@ -286,8 +289,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   // The database has now been created. Time to try to update it.
   EXPECT_TRUE(v4_database_);
@@ -304,8 +306,7 @@
   v4_database_->ApplyUpdate(std::move(parsed_server_response),
                             callback_db_updated_);
 
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   VerifyExpectedStoresState(false);
 }
@@ -317,8 +318,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   // The database has now been created. Time to try to update it.
   EXPECT_TRUE(v4_database_);
@@ -333,8 +333,7 @@
   v4_database_->ApplyUpdate(
       CreateFakeServerResponse(expected_store_state_map_, false),
       callback_db_updated_);
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
 
   VerifyExpectedStoresState(false);
 }
@@ -347,9 +346,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 
   StoresToCheck stores_to_check({linux_malware_id_, win_malware_id_});
@@ -372,9 +369,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 
   StoreAndHashPrefixes store_and_hash_prefixes;
@@ -393,9 +388,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 
   // Set the store corresponding to linux_malware_id_ to match the full hash.
@@ -422,9 +415,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 
   // Don't add win_malware_id_ to the StoresToCheck.
@@ -443,9 +434,7 @@
   V4Database::Create(task_runner_, database_dirname_, list_infos_,
                      callback_db_ready_);
   created_but_not_called_back_ = true;
-  task_runner_->RunPendingTasks();
-
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_EQ(true, created_and_called_back_);
 
   // verify_checksum_called_back_ set to false in the constructor.
@@ -457,8 +446,7 @@
   // verify_checksum_called_back_ should still be false since the checksum
   // verification is async.
   EXPECT_FALSE(verify_checksum_called_back_);
-  task_runner_->RunPendingTasks();
-  base::RunLoop().RunUntilIdle();
+  WaitForTasksOnTaskRunner();
   EXPECT_TRUE(verify_checksum_called_back_);
 }
 
diff --git a/components/safe_browsing_db/v4_local_database_manager.cc b/components/safe_browsing_db/v4_local_database_manager.cc
index 65916ba..aca22fc 100644
--- a/components/safe_browsing_db/v4_local_database_manager.cc
+++ b/components/safe_browsing_db/v4_local_database_manager.cc
@@ -26,6 +26,8 @@
     std::numeric_limits<ThreatSeverity>::max();
 
 ListInfos GetListInfos() {
+  // NOTE(vakh): When adding a store here, add the corresponding store-specific
+  // histograms also.
   return ListInfos(
       {ListInfo(true, "UrlMalware.store", GetUrlMalwareId(),
                 SB_THREAT_TYPE_URL_MALWARE),
@@ -79,7 +81,10 @@
 }
 
 V4LocalDatabaseManager::V4LocalDatabaseManager(const base::FilePath& base_path)
-    : base_path_(base_path), enabled_(false), list_infos_(GetListInfos()) {
+    : base_path_(base_path),
+      enabled_(false),
+      list_infos_(GetListInfos()),
+      weak_factory_(this) {
   DCHECK(!base_path_.empty());
   DCHECK(!list_infos_.empty());
 
@@ -149,7 +154,13 @@
     return true;
   }
 
-  PerformFullHashCheck(std::move(check), full_hash_to_store_and_hash_prefixes);
+  // Post the task to check full hashes back on the IO thread to follow the
+  // documented behavior of CheckBrowseUrl.
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::Bind(&V4LocalDatabaseManager::PerformFullHashCheck, this,
+                 base::Passed(std::move(check)),
+                 full_hash_to_store_and_hash_prefixes));
   return false;
 }
 
@@ -239,7 +250,7 @@
   SafeBrowsingDatabaseManager::StartOnIOThread(request_context_getter, config);
 
   db_updated_callback_ = base::Bind(&V4LocalDatabaseManager::DatabaseUpdated,
-                                    base::Unretained(this));
+                                    weak_factory_.GetWeakPtr());
 
   SetupUpdateProtocolManager(request_context_getter, config);
   SetupDatabase();
@@ -289,7 +300,7 @@
     // database updates.
     v4_database_->VerifyChecksum(
         base::Bind(&V4LocalDatabaseManager::DatabaseReadyForUpdates,
-                   base::Unretained(this)));
+                   weak_factory_.GetWeakPtr()));
 
     ProcessQueuedChecks();
   } else {
@@ -424,7 +435,7 @@
   v4_get_hash_protocol_manager_->GetFullHashes(
       full_hash_to_store_and_hash_prefixes,
       base::Bind(&V4LocalDatabaseManager::OnFullHashResponse,
-                 base::Unretained(this), base::Passed(std::move(check))));
+                 weak_factory_.GetWeakPtr(), base::Passed(std::move(check))));
 }
 
 void V4LocalDatabaseManager::ProcessQueuedChecks() {
@@ -472,16 +483,18 @@
   // Do not create the database on the IO thread since this may be an expensive
   // operation. Instead, do that on the task_runner and when the new database
   // has been created, swap it out on the IO thread.
-  NewDatabaseReadyCallback db_ready_callback = base::Bind(
-      &V4LocalDatabaseManager::DatabaseReadyForChecks, base::Unretained(this));
+  NewDatabaseReadyCallback db_ready_callback =
+      base::Bind(&V4LocalDatabaseManager::DatabaseReadyForChecks,
+                 weak_factory_.GetWeakPtr());
   V4Database::Create(task_runner_, base_path_, list_infos_, db_ready_callback);
 }
 
 void V4LocalDatabaseManager::SetupUpdateProtocolManager(
     net::URLRequestContextGetter* request_context_getter,
     const V4ProtocolConfig& config) {
-  V4UpdateCallback callback = base::Bind(
-      &V4LocalDatabaseManager::UpdateRequestCompleted, base::Unretained(this));
+  V4UpdateCallback callback =
+      base::Bind(&V4LocalDatabaseManager::UpdateRequestCompleted,
+                 weak_factory_.GetWeakPtr());
 
   v4_update_protocol_manager_ =
       V4UpdateProtocolManager::Create(request_context_getter, config, callback);
diff --git a/components/safe_browsing_db/v4_local_database_manager.h b/components/safe_browsing_db/v4_local_database_manager.h
index 6b9a997..a5bdef4 100644
--- a/components/safe_browsing_db/v4_local_database_manager.h
+++ b/components/safe_browsing_db/v4_local_database_manager.h
@@ -10,6 +10,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "components/safe_browsing_db/database_manager.h"
 #include "components/safe_browsing_db/hit_report.h"
 #include "components/safe_browsing_db/v4_database.h"
@@ -70,6 +71,13 @@
   // Must be initialized by calling StartOnIOThread() before using.
   V4LocalDatabaseManager(const base::FilePath& base_path);
 
+  ~V4LocalDatabaseManager() override;
+
+  void SetTaskRunnerForTest(
+      const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
+    task_runner_ = task_runner;
+  }
+
   enum class ClientCallbackType {
     // This represents the case when we're trying to determine if a URL is
     // unsafe from the following perspectives: Malware, Phishing, UwS.
@@ -120,10 +128,6 @@
 
  private:
   friend class V4LocalDatabaseManagerTest;
-  void SetTaskRunnerForTest(
-      const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
-    task_runner_ = task_runner;
-  }
   FRIEND_TEST_ALL_PREFIXES(V4LocalDatabaseManagerTest,
                            TestGetSeverestThreatTypeAndMetadata);
 
@@ -131,8 +135,6 @@
   // which clients have cancelled their outstanding request.
   typedef std::unordered_set<Client*> PendingClients;
 
-  ~V4LocalDatabaseManager() override;
-
   // Called when all the stores managed by the database have been read from
   // disk after startup and the database is ready for checking resource
   // reputation.
@@ -170,9 +172,9 @@
                           const std::vector<FullHashInfo>& full_hash_infos);
 
   // Performs the full hash checking of the URL in |check|.
-  void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
-                            const FullHashToStoreAndHashPrefixesMap&
-                                full_hash_to_store_and_hash_prefixes);
+  virtual void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
+                                    const FullHashToStoreAndHashPrefixesMap&
+                                        full_hash_to_store_and_hash_prefixes);
 
   // When the database is ready to use, process the checks that were queued
   // while the database was loading from disk.
@@ -234,6 +236,8 @@
   // The protocol manager that downloads the hash prefix updates.
   std::unique_ptr<V4UpdateProtocolManager> v4_update_protocol_manager_;
 
+  base::WeakPtrFactory<V4LocalDatabaseManager> weak_factory_;
+
   friend class base::RefCountedThreadSafe<V4LocalDatabaseManager>;
   DISALLOW_COPY_AND_ASSIGN(V4LocalDatabaseManager);
 };  // class V4LocalDatabaseManager
diff --git a/components/safe_browsing_db/v4_local_database_manager_unittest.cc b/components/safe_browsing_db/v4_local_database_manager_unittest.cc
index 4c572bc..b6659f4b1d 100644
--- a/components/safe_browsing_db/v4_local_database_manager_unittest.cc
+++ b/components/safe_browsing_db/v4_local_database_manager_unittest.cc
@@ -79,6 +79,31 @@
   GURL expected_url;
 };
 
+class FakeV4LocalDatabaseManager : public V4LocalDatabaseManager {
+ public:
+  void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
+                            const FullHashToStoreAndHashPrefixesMap&
+                                full_hash_to_store_and_hash_prefixes) override {
+    perform_full_hash_check_called_ = true;
+  }
+
+  FakeV4LocalDatabaseManager(const base::FilePath& base_path)
+      : V4LocalDatabaseManager(base_path),
+        perform_full_hash_check_called_(false) {}
+
+  static bool PerformFullHashCheckCalled(
+      scoped_refptr<safe_browsing::V4LocalDatabaseManager>& v4_ldbm) {
+    FakeV4LocalDatabaseManager* fake =
+        static_cast<FakeV4LocalDatabaseManager*>(v4_ldbm.get());
+    return fake->perform_full_hash_check_called_;
+  }
+
+ private:
+  ~FakeV4LocalDatabaseManager() override {}
+
+  bool perform_full_hash_check_called_;
+};
+
 class V4LocalDatabaseManagerTest : public PlatformTest {
  public:
   V4LocalDatabaseManagerTest() : task_runner_(new base::TestSimpleTaskRunner) {}
@@ -91,7 +116,7 @@
 
     v4_local_database_manager_ =
         make_scoped_refptr(new V4LocalDatabaseManager(base_dir_.GetPath()));
-    v4_local_database_manager_->SetTaskRunnerForTest(task_runner_);
+    SetTaskRunnerForTest();
 
     StartLocalDatabaseManager();
   }
@@ -138,16 +163,21 @@
     V4Database::Destroy(std::move(v4_local_database_manager_->v4_database_));
   }
 
+  void SetTaskRunnerForTest() {
+    v4_local_database_manager_->SetTaskRunnerForTest(task_runner_);
+  }
+
   void StartLocalDatabaseManager() {
     v4_local_database_manager_->StartOnIOThread(NULL, V4ProtocolConfig());
   }
 
   void StopLocalDatabaseManager() {
-    v4_local_database_manager_->StopOnIOThread(true);
+    if (v4_local_database_manager_) {
+      v4_local_database_manager_->StopOnIOThread(true);
+    }
 
     // Force destruction of the database.
-    task_runner_->RunPendingTasks();
-    base::RunLoop().RunUntilIdle();
+    WaitForTasksOnTaskRunner();
   }
 
   void WaitForTasksOnTaskRunner() {
@@ -205,6 +235,9 @@
   // The fake database returns a matched hash prefix.
   EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
       GURL("http://example.com/a/"), nullptr));
+
+  // Wait for PerformFullHashCheck to complete.
+  WaitForTasksOnTaskRunner();
 }
 
 TEST_F(V4LocalDatabaseManagerTest,
@@ -268,4 +301,66 @@
   EXPECT_TRUE(GetQueuedChecks().empty());
 }
 
+// This test is somewhat similar to TestCheckBrowseUrlWithFakeDbReturnsMatch but
+// it uses a fake V4LocalDatabaseManager to assert that PerformFullHashCheck is
+// called async.
+TEST_F(V4LocalDatabaseManagerTest, PerformFullHashCheckCalledAsync) {
+  // StopLocalDatabaseManager before resetting it because that's what
+  // ~V4LocalDatabaseManager expects.
+  StopLocalDatabaseManager();
+  v4_local_database_manager_ =
+      make_scoped_refptr(new FakeV4LocalDatabaseManager(base_dir_.GetPath()));
+  SetTaskRunnerForTest();
+  StartLocalDatabaseManager();
+  WaitForTasksOnTaskRunner();
+  net::TestURLFetcherFactory factory;
+
+  StoreAndHashPrefixes store_and_hash_prefixes;
+  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), HashPrefix("aaaa"));
+  ReplaceV4Database(store_and_hash_prefixes);
+
+  // The fake database returns a matched hash prefix.
+  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
+      GURL("http://example.com/a/"), nullptr));
+
+  EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
+      v4_local_database_manager_));
+
+  // Wait for PerformFullHashCheck to complete.
+  WaitForTasksOnTaskRunner();
+
+  EXPECT_TRUE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
+      v4_local_database_manager_));
+}
+
+TEST_F(V4LocalDatabaseManagerTest, UsingWeakPtrDropsCallback) {
+  // StopLocalDatabaseManager before resetting it because that's what
+  // ~V4LocalDatabaseManager expects.
+  StopLocalDatabaseManager();
+  v4_local_database_manager_ =
+      make_scoped_refptr(new FakeV4LocalDatabaseManager(base_dir_.GetPath()));
+  SetTaskRunnerForTest();
+  StartLocalDatabaseManager();
+  WaitForTasksOnTaskRunner();
+  net::TestURLFetcherFactory factory;
+
+  StoreAndHashPrefixes store_and_hash_prefixes;
+  store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), HashPrefix("aaaa"));
+  ReplaceV4Database(store_and_hash_prefixes);
+
+  // The fake database returns a matched hash prefix.
+  EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
+      GURL("http://example.com/a/"), nullptr));
+  v4_local_database_manager_->StopOnIOThread(true);
+
+  // Release the V4LocalDatabaseManager object right away before the callback
+  // gets called. When the callback gets called, without using a weak-ptr
+  // factory, this leads to a use after free. However, using the weak-ptr means
+  // that the callback is simply dropped.
+  v4_local_database_manager_ = nullptr;
+
+  // Wait for the tasks scheduled by StopOnIOThread to complete.
+  WaitForTasksOnTaskRunner();
+}
+
 }  // namespace safe_browsing
diff --git a/components/safe_browsing_db/v4_store.cc b/components/safe_browsing_db/v4_store.cc
index 81f30ec7..49ec3c5 100644
--- a/components/safe_browsing_db/v4_store.cc
+++ b/components/safe_browsing_db/v4_store.cc
@@ -36,8 +36,12 @@
 // Part 3: Represent the unit of value being measured and logged.
 const char kResult[] = ".Result";
 const char kTime[] = ".Time";
+// Part 4 (optional): Represent the name of the list for which the metric is
+// being logged. For instance, ".UrlSoceng".
 // UMA metric names for this file are generated by appending one value each,
-// in order, from parts 1, 2, and 3.
+// in order, from parts [1, 2, and 3], or [1, 2, 3, and 4]. For example:
+// SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdate.Result, or
+// SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdate.Result.UrlSoceng
 
 const uint32_t kFileMagic = 0x600D71FE;
 const uint32_t kFileVersion = 9;
@@ -65,7 +69,7 @@
 
   std::string suffix = GetUmaSuffixForStore(file_path);
   base::HistogramBase* histogram_suffix = base::Histogram::FactoryTimeGet(
-      metric + suffix + kTime, base::TimeDelta::FromMilliseconds(1),
+      metric + kTime + suffix, base::TimeDelta::FromMilliseconds(1),
       base::TimeDelta::FromMinutes(1), kBucketCount,
       base::HistogramBase::kUmaTargetedHistogramFlag);
   if (histogram_suffix) {
@@ -89,7 +93,7 @@
 
   std::string suffix = GetUmaSuffixForStore(file_path);
   base::HistogramBase* histogram_suffix = base::LinearHistogram::FactoryGet(
-      metric + suffix + kResult, 1, maximum, maximum + 1,
+      metric + kResult + suffix, 1, maximum, maximum + 1,
       base::HistogramBase::kUmaTargetedHistogramFlag);
   if (histogram_suffix) {
     histogram_suffix->Add(value);
@@ -194,7 +198,9 @@
                  const base::FilePath& store_path)
     : store_path_(store_path), task_runner_(task_runner) {}
 
-V4Store::~V4Store() {}
+V4Store::~V4Store() {
+  DCHECK(task_runner_->RunsTasksOnCurrentThread());
+}
 
 std::string V4Store::DebugString() const {
   std::string state_base64;
@@ -204,6 +210,14 @@
                             store_path_.value().c_str(), state_base64.c_str());
 }
 
+// static
+void V4Store::Destroy(std::unique_ptr<V4Store> v4_store) {
+  V4Store* v4_store_raw = v4_store.release();
+  if (v4_store_raw) {
+    v4_store_raw->task_runner_->DeleteSoon(FROM_HERE, v4_store_raw);
+  }
+}
+
 void V4Store::Reset() {
   expected_checksum_.clear();
   hash_prefix_map_.clear();
diff --git a/components/safe_browsing_db/v4_store.h b/components/safe_browsing_db/v4_store.h
index 1b9e4d6..9775ee7 100644
--- a/components/safe_browsing_db/v4_store.h
+++ b/components/safe_browsing_db/v4_store.h
@@ -176,6 +176,10 @@
 
   std::string DebugString() const;
 
+  // Schedules the destruction of the V4Store object pointed to by |v4_store|,
+  // on the task runner.
+  static void Destroy(std::unique_ptr<V4Store> v4_store);
+
   // Reads the store file from disk and populates the in-memory representation
   // of the hash prefixes.
   void Initialize();
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index d6c5cb96..c7d94214 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -2115,8 +2115,8 @@
   if (!loaded_)
     return;
 
-  FOR_EACH_OBSERVER(TemplateURLServiceObserver, model_observers_,
-                    OnTemplateURLServiceChanged());
+  for (auto& observer : model_observers_)
+    observer.OnTemplateURLServiceChanged();
 }
 
 // |template_urls| are the TemplateURLs loaded from the database.
diff --git a/components/search_provider_logos/logo_tracker.cc b/components/search_provider_logos/logo_tracker.cc
index 2027d8a..0facd981 100644
--- a/components/search_provider_logos/logo_tracker.cc
+++ b/components/search_provider_logos/logo_tracker.cc
@@ -157,7 +157,8 @@
   is_cached_logo_valid_ = false;
 
   // Clear obsevers.
-  FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnObserverRemoved());
+  for (auto& observer : logo_observers_)
+    observer.OnObserverRemoved();
   logo_observers_.Clear();
 }
 
@@ -186,7 +187,8 @@
   }
   is_cached_logo_valid_ = true;
   Logo* logo = cached_logo_.get();
-  FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo, true));
+  for (auto& observer : logo_observers_)
+    observer.OnLogoAvailable(logo, true);
   FetchLogo();
 }
 
@@ -294,9 +296,8 @@
     // Notify observers if a new logo was fetched, or if the new logo is NULL
     // but the cached logo was non-NULL.
     if (logo || cached_logo_) {
-      FOR_EACH_OBSERVER(LogoObserver,
-                        logo_observers_,
-                        OnLogoAvailable(logo.get(), false));
+      for (auto& observer : logo_observers_)
+        observer.OnLogoAvailable(logo.get(), false);
       SetCachedLogo(std::move(encoded_logo));
     }
   }
diff --git a/components/session_manager/OWNERS b/components/session_manager/OWNERS
index e7a8a06..b280edc9 100644
--- a/components/session_manager/OWNERS
+++ b/components/session_manager/OWNERS
@@ -1,2 +1,3 @@
 achuith@chromium.org
 alemate@chromium.org
+xiyuan@chromium.org
diff --git a/components/sessions/core/tab_restore_service_helper.cc b/components/sessions/core/tab_restore_service_helper.cc
index 05094d2..841f075 100644
--- a/components/sessions/core/tab_restore_service_helper.cc
+++ b/components/sessions/core/tab_restore_service_helper.cc
@@ -51,8 +51,8 @@
 }
 
 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
-  FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
-                    TabRestoreServiceDestroyed(tab_restore_service_));
+  for (auto& observer : observer_list_)
+    observer.TabRestoreServiceDestroyed(tab_restore_service_);
 }
 
 void TabRestoreServiceHelper::AddObserver(
@@ -265,13 +265,13 @@
 }
 
 void TabRestoreServiceHelper::NotifyTabsChanged() {
-  FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
-                    TabRestoreServiceChanged(tab_restore_service_));
+  for (auto& observer : observer_list_)
+    observer.TabRestoreServiceChanged(tab_restore_service_);
 }
 
 void TabRestoreServiceHelper::NotifyLoaded() {
-  FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
-                    TabRestoreServiceLoaded(tab_restore_service_));
+  for (auto& observer : observer_list_)
+    observer.TabRestoreServiceLoaded(tab_restore_service_);
 }
 
 void TabRestoreServiceHelper::AddEntry(std::unique_ptr<Entry> entry,
diff --git a/components/ssl_errors/BUILD.gn b/components/ssl_errors/BUILD.gn
index d58ffa3..eda7b32f 100644
--- a/components/ssl_errors/BUILD.gn
+++ b/components/ssl_errors/BUILD.gn
@@ -31,6 +31,7 @@
     ":ssl_errors",
     "//base",
     "//components/network_time",
+    "//components/network_time:network_time_test_support",
     "//components/prefs:test_support",
     "//net:test_support",
     "//testing/gtest",
diff --git a/components/ssl_errors/error_classification.cc b/components/ssl_errors/error_classification.cc
index 66aa42d..c61b326 100644
--- a/components/ssl_errors/error_classification.cc
+++ b/components/ssl_errors/error_classification.cc
@@ -38,39 +38,6 @@
 namespace ssl_errors {
 namespace {
 
-// Describes the result of getting network time and if it was
-// unavailable, why it was unavailable. This enum is being histogrammed
-// so do not reorder or remove values.
-enum NetworkClockState {
-  // Value 0 was NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC, which is obsolete
-  // in favor of the finer-grained values below.
-
-  // The clock state relative to network time is unknown because the
-  // user's clock has fallen out of sync with the latest information
-  // from the network (due to e.g. suspend/resume).
-  NETWORK_CLOCK_STATE_UNKNOWN_SYNC_LOST = 1,
-  // The clock is "close enough" to the network time.
-  NETWORK_CLOCK_STATE_OK,
-  // The clock is in the past relative to network time.
-  NETWORK_CLOCK_STATE_CLOCK_IN_PAST,
-  // The clock is in the future relative to network time.
-  NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE,
-  // The clock state relative to network time is unknown because no sync
-  // attempt has been made yet.
-  NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT,
-  // The clock state relative to network time is unknown because one or
-  // more sync attempts has failed.
-  NETWORK_CLOCK_STATE_UNKNOWN_NO_SUCCESSFUL_SYNC,
-  // The clock state relative to network time is unknown because the
-  // first sync attempt is still pending.
-  NETWORK_CLOCK_STATE_UNKNOWN_FIRST_SYNC_PENDING,
-  // The clock state relative to network time is unknown because one or
-  // more time query attempts have failed, and a subsequent sync attempt
-  // is still pending.
-  NETWORK_CLOCK_STATE_UNKNOWN_SUBSEQUENT_SYNC_PENDING,
-  NETWORK_CLOCK_STATE_MAX
-};
-
 // Events for UMA. Do not reorder or change!
 enum SSLInterstitialCause {
   CLOCK_PAST,
@@ -280,8 +247,8 @@
     build_time_state = CLOCK_STATE_FUTURE;
   }
 
-  UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.clockstate.network2",
-                            network_time_result, NETWORK_CLOCK_STATE_MAX);
+  UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.clockstate.network3",
+                            network_state, NETWORK_CLOCK_STATE_MAX);
   UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.clockstate.build_time",
                             build_time_state, CLOCK_STATE_MAX);
 
diff --git a/components/ssl_errors/error_classification.h b/components/ssl_errors/error_classification.h
index 9bcaeae..7da66fdd 100644
--- a/components/ssl_errors/error_classification.h
+++ b/components/ssl_errors/error_classification.h
@@ -46,6 +46,41 @@
   CLOCK_STATE_MAX,
 };
 
+// Describes the result of getting network time and if it was
+// unavailable, why it was unavailable. This enum is being histogrammed
+// so do not reorder or remove values.
+//
+// Exposed for testing.
+enum NetworkClockState {
+  // Value 0 was NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC, which is obsolete
+  // in favor of the finer-grained values below.
+
+  // The clock state relative to network time is unknown because the
+  // user's clock has fallen out of sync with the latest information
+  // from the network (due to e.g. suspend/resume).
+  NETWORK_CLOCK_STATE_UNKNOWN_SYNC_LOST = 1,
+  // The clock is "close enough" to the network time.
+  NETWORK_CLOCK_STATE_OK,
+  // The clock is in the past relative to network time.
+  NETWORK_CLOCK_STATE_CLOCK_IN_PAST,
+  // The clock is in the future relative to network time.
+  NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE,
+  // The clock state relative to network time is unknown because no sync
+  // attempt has been made yet.
+  NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT,
+  // The clock state relative to network time is unknown because one or
+  // more sync attempts has failed.
+  NETWORK_CLOCK_STATE_UNKNOWN_NO_SUCCESSFUL_SYNC,
+  // The clock state relative to network time is unknown because the
+  // first sync attempt is still pending.
+  NETWORK_CLOCK_STATE_UNKNOWN_FIRST_SYNC_PENDING,
+  // The clock state relative to network time is unknown because one or
+  // more time query attempts have failed, and a subsequent sync attempt
+  // is still pending.
+  NETWORK_CLOCK_STATE_UNKNOWN_SUBSEQUENT_SYNC_PENDING,
+  NETWORK_CLOCK_STATE_MAX
+};
+
 // Compares |now_system| to the build time and to the current network time, and
 // returns an inference about the state of the system clock.  A result from
 // network time, if available, will always be preferred to a result from the
diff --git a/components/ssl_errors/error_classification_unittest.cc b/components/ssl_errors/error_classification_unittest.cc
index 425a9f2e..e436f4e 100644
--- a/components/ssl_errors/error_classification_unittest.cc
+++ b/components/ssl_errors/error_classification_unittest.cc
@@ -8,21 +8,41 @@
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/string_split.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/simple_test_tick_clock.h"
 #include "base/time/default_clock.h"
 #include "base/time/default_tick_clock.h"
+#include "components/network_time/network_time_test_utils.h"
 #include "components/network_time/network_time_tracker.h"
 #include "components/prefs/testing_pref_service.h"
 #include "net/base/net_errors.h"
 #include "net/cert/x509_cert_types.h"
 #include "net/cert/x509_certificate.h"
 #include "net/test/cert_test_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "net/test/test_certificate_data.h"
 #include "net/test/test_data_directory.h"
 #include "net/url_request/url_request_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-class SSLErrorClassificationTest : public testing::Test {};
+namespace {
+const char kNetworkTimeHistogram[] = "interstitial.ssl.clockstate.network3";
+
+static std::unique_ptr<net::test_server::HttpResponse>
+NetworkErrorResponseHandler(const net::test_server::HttpRequest& request) {
+  return std::unique_ptr<net::test_server::HttpResponse>(
+      new net::test_server::RawHttpResponse("", ""));
+}
+
+}  // namespace
+
+class SSLErrorClassificationTest : public network_time::FieldTrialTest {
+ public:
+  SSLErrorClassificationTest() : network_time::FieldTrialTest() {}
+};
 
 TEST_F(SSLErrorClassificationTest, TestNameMismatch) {
   scoped_refptr<net::X509Certificate> google_cert(
@@ -189,6 +209,10 @@
 TEST_F(SSLErrorClassificationTest, GetClockState) {
   // This test aims to obtain all possible return values of
   // |GetClockState|.
+  const char kBuildTimeHistogram[] = "interstitial.ssl.clockstate.build_time";
+  base::HistogramTester histograms;
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 0);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 0);
   TestingPrefServiceSimple pref_service;
   network_time::NetworkTimeTracker::RegisterPrefs(pref_service.registry());
   base::MessageLoop loop;
@@ -201,18 +225,39 @@
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 1);
+  histograms.ExpectBucketCount(kBuildTimeHistogram,
+                               ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, 1);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 1);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 1);
 
   ssl_errors::SetBuildTimeForTesting(base::Time::Now() -
                                      base::TimeDelta::FromDays(367));
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_FUTURE,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 2);
+  histograms.ExpectBucketCount(kBuildTimeHistogram,
+                               ssl_errors::ClockState::CLOCK_STATE_FUTURE, 1);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 2);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 2);
 
   ssl_errors::SetBuildTimeForTesting(base::Time::Now() +
                                      base::TimeDelta::FromDays(3));
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_PAST,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 3);
+  histograms.ExpectBucketCount(kBuildTimeHistogram,
+                               ssl_errors::ClockState::CLOCK_STATE_FUTURE, 1);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 3);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 3);
 
   // Intentionally leave the build time alone.  It should be ignored
   // in favor of network time.
@@ -224,6 +269,10 @@
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_PAST,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 4);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 4);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_PAST, 1);
 
   network_time_tracker.UpdateNetworkTime(
       base::Time::Now() - base::TimeDelta::FromHours(1),
@@ -233,6 +282,11 @@
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_FUTURE,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 5);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 5);
+  histograms.ExpectBucketCount(kNetworkTimeHistogram,
+                               ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE,
+                               1);
 
   network_time_tracker.UpdateNetworkTime(
       base::Time::Now(),
@@ -242,6 +296,10 @@
   EXPECT_EQ(
       ssl_errors::ClockState::CLOCK_STATE_OK,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kBuildTimeHistogram, 6);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 6);
+  histograms.ExpectBucketCount(kNetworkTimeHistogram,
+                               ssl_errors::NETWORK_CLOCK_STATE_OK, 1);
 
   // Now clear the network time.  The build time should reassert
   // itself.
@@ -263,3 +321,123 @@
       ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
       ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
 }
+
+// Tests that all possible NetworkClockState histogram values are recorded
+// appropriately.
+TEST_F(SSLErrorClassificationTest, NetworkClockStateHistogram) {
+  base::Thread io_thread("IO thread");
+  base::Thread::Options thread_options;
+  thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+  EXPECT_TRUE(io_thread.StartWithOptions(thread_options));
+
+  net::EmbeddedTestServer test_server;
+  ASSERT_TRUE(test_server.Start());
+
+  base::HistogramTester histograms;
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 0);
+  TestingPrefServiceSimple pref_service;
+  network_time::NetworkTimeTracker::RegisterPrefs(pref_service.registry());
+  base::SimpleTestTickClock* tick_clock = new base::SimpleTestTickClock;
+  base::SimpleTestClock* clock = new base::SimpleTestClock;
+  // Do this to be sure that |is_null| returns false.
+  clock->Advance(base::TimeDelta::FromDays(111));
+  tick_clock->Advance(base::TimeDelta::FromDays(222));
+
+  base::MessageLoop loop;
+  network_time::NetworkTimeTracker network_time_tracker(
+      std::unique_ptr<base::Clock>(clock),
+      std::unique_ptr<base::TickClock>(tick_clock), &pref_service,
+      new net::TestURLRequestContextGetter(io_thread.task_runner()));
+  network_time_tracker.SetTimeServerURLForTesting(test_server.GetURL("/"));
+  SetNetworkQueriesWithVariationsService(true, 0.0);
+
+  // No sync attempt.
+  EXPECT_EQ(
+      ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
+      ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 1);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 1);
+
+  // First sync attempt is pending.
+  test_server.RegisterRequestHandler(base::Bind(&NetworkErrorResponseHandler));
+  EXPECT_TRUE(network_time_tracker.QueryTimeServiceForTesting());
+  EXPECT_EQ(
+      ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
+      ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 2);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_FIRST_SYNC_PENDING, 1);
+  network_time_tracker.WaitForFetchForTesting(123123123);
+
+  // No successful sync.
+  EXPECT_EQ(
+      ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
+      ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 3);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SUCCESSFUL_SYNC, 1);
+
+  // Subsequent sync attempt is pending.
+  EXPECT_TRUE(network_time_tracker.QueryTimeServiceForTesting());
+  EXPECT_EQ(
+      ssl_errors::ClockState::CLOCK_STATE_UNKNOWN,
+      ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 4);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram,
+      ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_SUBSEQUENT_SYNC_PENDING, 1);
+  network_time_tracker.WaitForFetchForTesting(123123123);
+
+  // System clock is correct.
+  network_time_tracker.UpdateNetworkTime(
+      clock->Now(),
+      base::TimeDelta::FromSeconds(1),         // resolution
+      base::TimeDelta::FromMilliseconds(250),  // latency
+      tick_clock->NowTicks());                 // posting time
+  EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_OK,
+            ssl_errors::GetClockState(clock->Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 5);
+  histograms.ExpectBucketCount(kNetworkTimeHistogram,
+                               ssl_errors::NETWORK_CLOCK_STATE_OK, 1);
+
+  // System clock is in the past.
+  network_time_tracker.UpdateNetworkTime(
+      clock->Now() + base::TimeDelta::FromHours(1),
+      base::TimeDelta::FromSeconds(1),         // resolution
+      base::TimeDelta::FromMilliseconds(250),  // latency
+      tick_clock->NowTicks());                 // posting time
+  EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_PAST,
+            ssl_errors::GetClockState(clock->Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 6);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_PAST, 1);
+
+  // System clock is in the future.
+  network_time_tracker.UpdateNetworkTime(
+      clock->Now() - base::TimeDelta::FromHours(1),
+      base::TimeDelta::FromSeconds(1),         // resolution
+      base::TimeDelta::FromMilliseconds(250),  // latency
+      tick_clock->NowTicks());                 // posting time
+  EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_FUTURE,
+            ssl_errors::GetClockState(clock->Now(), &network_time_tracker));
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 7);
+  histograms.ExpectBucketCount(kNetworkTimeHistogram,
+                               ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE,
+                               1);
+
+  // Sync has been lost.
+  tick_clock->Advance(base::TimeDelta::FromSeconds(1));
+  clock->Advance(base::TimeDelta::FromDays(1));
+  // GetClockState() will fall back to the build time heuristic.
+  ssl_errors::GetClockState(clock->Now(), &network_time_tracker);
+  histograms.ExpectTotalCount(kNetworkTimeHistogram, 8);
+  histograms.ExpectBucketCount(
+      kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_SYNC_LOST,
+      1);
+
+  io_thread.Stop();
+}
diff --git a/components/syncable_prefs/pref_model_associator.cc b/components/syncable_prefs/pref_model_associator.cc
index 08392b3..a704131 100644
--- a/components/syncable_prefs/pref_model_associator.cc
+++ b/components/syncable_prefs/pref_model_associator.cc
@@ -538,8 +538,8 @@
   if (observer_iter == synced_pref_observers_.end())
     return;
   SyncedPrefObserverList* observers = observer_iter->second.get();
-  FOR_EACH_OBSERVER(SyncedPrefObserver, *observers,
-                    OnSyncedPrefChanged(path, from_sync));
+  for (auto& observer : *observers)
+    observer.OnSyncedPrefChanged(path, from_sync);
 }
 
 }  // namespace syncable_prefs
diff --git a/components/syncable_prefs/pref_service_syncable.cc b/components/syncable_prefs/pref_service_syncable.cc
index ab5e156..3aca8af 100644
--- a/components/syncable_prefs/pref_service_syncable.cc
+++ b/components/syncable_prefs/pref_service_syncable.cc
@@ -180,8 +180,8 @@
 }
 
 void PrefServiceSyncable::OnIsSyncingChanged() {
-  FOR_EACH_OBSERVER(PrefServiceSyncableObserver, observer_list_,
-                    OnIsSyncingChanged());
+  for (auto& observer : observer_list_)
+    observer.OnIsSyncingChanged();
 }
 
 void PrefServiceSyncable::ProcessPrefChange(const std::string& name) {
diff --git a/components/toolbar/toolbar_model_impl.cc b/components/toolbar/toolbar_model_impl.cc
index 5e795f8..1d6b117 100644
--- a/components/toolbar/toolbar_model_impl.cc
+++ b/components/toolbar/toolbar_model_impl.cc
@@ -110,6 +110,8 @@
 
 base::string16 ToolbarModelImpl::GetSecureVerboseText() const {
   switch (GetSecurityLevel(false)) {
+    case SecurityStateModel::HTTP_SHOW_WARNING:
+      return l10n_util::GetStringUTF16(IDS_NOT_SECURE_VERBOSE_STATE);
     case SecurityStateModel::SECURE:
       return l10n_util::GetStringUTF16(IDS_SECURE_VERBOSE_STATE);
     case SecurityStateModel::DANGEROUS:
diff --git a/components/undo/undo_manager.cc b/components/undo/undo_manager.cc
index 59cb8e0..6829e52 100644
--- a/components/undo/undo_manager.cc
+++ b/components/undo/undo_manager.cc
@@ -203,8 +203,8 @@
 }
 
 void UndoManager::NotifyOnUndoManagerStateChange() {
-  FOR_EACH_OBSERVER(
-      UndoManagerObserver, observers_, OnUndoManagerStateChange());
+  for (auto& observer : observers_)
+    observer.OnUndoManagerStateChange();
 }
 
 void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
diff --git a/components/update_client/update_client.cc b/components/update_client/update_client.cc
index db4b21b..848e539f 100644
--- a/components/update_client/update_client.cc
+++ b/components/update_client/update_client.cc
@@ -187,7 +187,8 @@
 void UpdateClientImpl::NotifyObservers(Observer::Events event,
                                        const std::string& id) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  FOR_EACH_OBSERVER(Observer, observer_list_, OnEvent(event, id));
+  for (auto& observer : observer_list_)
+    observer.OnEvent(event, id);
 }
 
 bool UpdateClientImpl::GetCrxUpdateState(const std::string& id,
diff --git a/components/user_manager/user_manager_base.cc b/components/user_manager/user_manager_base.cc
index 562589ce..f3ddd7d84 100644
--- a/components/user_manager/user_manager_base.cc
+++ b/components/user_manager/user_manager_base.cc
@@ -677,8 +677,8 @@
 
 void UserManagerBase::NotifyLocalStateChanged() {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(
-      UserManager::Observer, observer_list_, LocalStateChanged(this));
+  for (auto& observer : observer_list_)
+    observer.LocalStateChanged(this);
 }
 
 bool UserManagerBase::CanUserBeRemoved(const User* user) const {
@@ -977,24 +977,21 @@
 
 void UserManagerBase::NotifyActiveUserChanged(const User* active_user) {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(UserManager::UserSessionStateObserver,
-                    session_state_observer_list_,
-                    ActiveUserChanged(active_user));
+  for (auto& observer : session_state_observer_list_)
+    observer.ActiveUserChanged(active_user);
 }
 
 void UserManagerBase::NotifyUserAddedToSession(const User* added_user,
                                                bool user_switch_pending) {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(UserManager::UserSessionStateObserver,
-                    session_state_observer_list_,
-                    UserAddedToSession(added_user));
+  for (auto& observer : session_state_observer_list_)
+    observer.UserAddedToSession(added_user);
 }
 
 void UserManagerBase::NotifyActiveUserHashChanged(const std::string& hash) {
   DCHECK(task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(UserManager::UserSessionStateObserver,
-                    session_state_observer_list_,
-                    ActiveUserHashChanged(hash));
+  for (auto& observer : session_state_observer_list_)
+    observer.ActiveUserHashChanged(hash);
 }
 
 void UserManagerBase::ChangeUserChildStatus(User* user, bool is_child) {
@@ -1005,9 +1002,8 @@
   SaveUserType(user->GetAccountId(), is_child
                                          ? user_manager::USER_TYPE_CHILD
                                          : user_manager::USER_TYPE_REGULAR);
-  FOR_EACH_OBSERVER(UserManager::UserSessionStateObserver,
-                    session_state_observer_list_,
-                    UserChangedChildStatus(user));
+  for (auto& observer : session_state_observer_list_)
+    observer.UserChangedChildStatus(user);
 }
 
 void UserManagerBase::Initialize() {
diff --git a/components/user_prefs/tracked/segregated_pref_store.cc b/components/user_prefs/tracked/segregated_pref_store.cc
index af8dfe3..edb1f20 100644
--- a/components/user_prefs/tracked/segregated_pref_store.cc
+++ b/components/user_prefs/tracked/segregated_pref_store.cc
@@ -24,8 +24,8 @@
   if (failed_sub_initializations_ + successful_sub_initializations_ < 2)
     return;
 
-  FOR_EACH_OBSERVER(PrefStore::Observer, outer_->observers_,
-                    OnPrefValueChanged(key));
+  for (auto& observer : outer_->observers_)
+    observer.OnPrefValueChanged(key);
 }
 
 void SegregatedPrefStore::AggregatingObserver::OnInitializationCompleted(
@@ -44,9 +44,8 @@
         outer_->read_error_delegate_->OnError(read_error);
     }
 
-    FOR_EACH_OBSERVER(
-        PrefStore::Observer, outer_->observers_,
-        OnInitializationCompleted(successful_sub_initializations_ == 2));
+    for (auto& observer : outer_->observers_)
+      observer.OnInitializationCompleted(successful_sub_initializations_ == 2);
   }
 }
 
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java
index 02fc45d..7b1c7bb8 100644
--- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedService.java
@@ -22,7 +22,7 @@
 public class VariationsSeedService extends IntentService {
     private static final String TAG = "VariationsSeedServ";
     private static final String VARIATIONS_SERVER_URL =
-            "https://clients4.google.com/chrome-variations/seed?osname=android";
+            "https://clientservices.googleapis.com/chrome-variations/seed?osname=android";
     private static final int BUFFER_SIZE = 4096;
     private static final int READ_TIMEOUT = 10000; // time in ms
     private static final int REQUEST_TIMEOUT = 15000; // time in ms
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 8f5d5df..6b95b26f 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -626,11 +626,11 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   if (result.kill_critical_group_change_count > 0) {
-    FOR_EACH_OBSERVER(Observer, observer_list_,
-                      OnExperimentChangesDetected(Observer::CRITICAL));
+    for (auto& observer : observer_list_)
+      observer.OnExperimentChangesDetected(Observer::CRITICAL);
   } else if (result.kill_best_effort_group_change_count > 0) {
-    FOR_EACH_OBSERVER(Observer, observer_list_,
-                      OnExperimentChangesDetected(Observer::BEST_EFFORT));
+    for (auto& observer : observer_list_)
+      observer.OnExperimentChangesDetected(Observer::BEST_EFFORT);
   }
 }
 
diff --git a/components/wallpaper/wallpaper_manager_base.cc b/components/wallpaper/wallpaper_manager_base.cc
index e0d38e3..9e2d2331 100644
--- a/components/wallpaper/wallpaper_manager_base.cc
+++ b/components/wallpaper/wallpaper_manager_base.cc
@@ -630,7 +630,8 @@
 }
 
 void WallpaperManagerBase::UpdateWallpaper(bool clear_cache) {
-  FOR_EACH_OBSERVER(Observer, observers_, OnUpdateWallpaperForTesting());
+  for (auto& observer : observers_)
+    observer.OnUpdateWallpaperForTesting();
   if (clear_cache)
     wallpaper_cache_.clear();
   SetUserWallpaperNow(last_selected_user_);
@@ -647,8 +648,8 @@
 }
 
 void WallpaperManagerBase::NotifyAnimationFinished() {
-  FOR_EACH_OBSERVER(Observer, observers_,
-                    OnWallpaperAnimationFinished(last_selected_user_));
+  for (auto& observer : observers_)
+    observer.OnWallpaperAnimationFinished(last_selected_user_);
 }
 
 // WallpaperManager, protected: -----------------------------------------------
diff --git a/components/wallpaper/wallpaper_resizer.cc b/components/wallpaper/wallpaper_resizer.cc
index 93640fd..545a9cc 100644
--- a/components/wallpaper/wallpaper_resizer.cc
+++ b/components/wallpaper/wallpaper_resizer.cc
@@ -144,7 +144,8 @@
 
 void WallpaperResizer::OnResizeFinished(SkBitmap* resized_bitmap) {
   image_ = gfx::ImageSkia::CreateFrom1xBitmap(*resized_bitmap);
-  FOR_EACH_OBSERVER(WallpaperResizerObserver, observers_, OnWallpaperResized());
+  for (auto& observer : observers_)
+    observer.OnWallpaperResized();
 }
 
 }  // namespace wallpaper
diff --git a/components/zoom/zoom_controller.cc b/components/zoom/zoom_controller.cc
index 1fa5f7f..a28a01d 100644
--- a/components/zoom/zoom_controller.cc
+++ b/components/zoom/zoom_controller.cc
@@ -127,8 +127,8 @@
     ZoomChangedEventData zoom_change_data(web_contents(), old_zoom_level,
                                           zoom_level_, zoom_mode_,
                                           can_show_bubble);
-    FOR_EACH_OBSERVER(ZoomObserver, observers_,
-                      OnZoomChanged(zoom_change_data));
+    for (auto& observer : observers_)
+      observer.OnZoomChanged(zoom_change_data);
 
     last_client_ = NULL;
     return true;
@@ -221,8 +221,8 @@
       } else {
         // When we don't call any HostZoomMap set functions, we send the event
         // manually.
-        FOR_EACH_OBSERVER(ZoomObserver, observers_,
-                          OnZoomChanged(*event_data_));
+        for (auto& observer : observers_)
+          observer.OnZoomChanged(*event_data_);
         event_data_.reset();
       }
       break;
@@ -238,8 +238,8 @@
       } else {
         // When we don't call any HostZoomMap set functions, we send the event
         // manually.
-        FOR_EACH_OBSERVER(ZoomObserver, observers_,
-                          OnZoomChanged(*event_data_));
+        for (auto& observer : observers_)
+          observer.OnZoomChanged(*event_data_);
         event_data_.reset();
       }
       break;
@@ -346,8 +346,8 @@
     // The zoom bubble should not be shown for zoom changes where the host
     // is empty.
     zoom_change_data.can_show_bubble = can_show_bubble_ && !host.empty();
-    FOR_EACH_OBSERVER(ZoomObserver, observers_,
-                      OnZoomChanged(zoom_change_data));
+    for (auto& observer : observers_)
+      observer.OnZoomChanged(zoom_change_data);
   } else {
     // TODO(wjmaclean) Should we consider having HostZoomMap send both old and
     // new zoom levels here?
@@ -356,8 +356,8 @@
     ZoomChangedEventData zoom_change_data(web_contents(), zoom_level,
                                           zoom_level, zoom_mode_,
                                           false /* can_show_bubble */);
-    FOR_EACH_OBSERVER(ZoomObserver, observers_,
-                      OnZoomChanged(zoom_change_data));
+    for (auto& observer : observers_)
+      observer.OnZoomChanged(zoom_change_data);
   }
 }
 
diff --git a/components/zoom/zoom_event_manager.cc b/components/zoom/zoom_event_manager.cc
index 5c328940..c1fd61c 100644
--- a/components/zoom/zoom_event_manager.cc
+++ b/components/zoom/zoom_event_manager.cc
@@ -37,8 +37,8 @@
 }
 
 void ZoomEventManager::OnDefaultZoomLevelChanged() {
-  FOR_EACH_OBSERVER(ZoomEventManagerObserver, observers_,
-                    OnDefaultZoomLevelChanged());
+  for (auto& observer : observers_)
+    observer.OnDefaultZoomLevelChanged();
 }
 
 void ZoomEventManager::AddZoomEventManagerObserver(
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 534bf7f3..00431273 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -848,6 +848,10 @@
     "media/media_web_contents_observer.h",
     "media/midi_host.cc",
     "media/midi_host.h",
+    "media/session/audio_focus_delegate.h",
+    "media/session/audio_focus_delegate_android.cc",
+    "media/session/audio_focus_delegate_android.h",
+    "media/session/audio_focus_delegate_default.cc",
     "media/session/audio_focus_manager.cc",
     "media/session/audio_focus_manager.h",
     "media/session/media_metadata_sanitizer.cc",
@@ -858,11 +862,7 @@
     "media/session/media_session_controller.h",
     "media/session/media_session_controllers_manager.cc",
     "media/session/media_session_controllers_manager.h",
-    "media/session/media_session_delegate.h",
-    "media/session/media_session_delegate_android.cc",
-    "media/session/media_session_delegate_android.h",
-    "media/session/media_session_delegate_default.cc",
-    "media/session/media_session_observer.h",
+    "media/session/media_session_player_observer.h",
     "media/session/media_session_service_impl.cc",
     "media/session/media_session_service_impl.h",
     "media/session/media_session_uma_helper.cc",
@@ -1717,9 +1717,9 @@
     sources -= [
       "browser_ipc_logging.cc",
       "device_sensors/data_fetcher_shared_memory_default.cc",
+      "media/session/audio_focus_delegate_default.cc",
       "media/session/audio_focus_manager.cc",
       "media/session/audio_focus_manager.h",
-      "media/session/media_session_delegate_default.cc",
       "power_usage_monitor_impl.cc",
       "power_usage_monitor_impl.h",
       "tracing/tracing_ui.cc",
diff --git a/content/browser/android/browser_jni_registrar.cc b/content/browser/android/browser_jni_registrar.cc
index 7985f7c..32dfc104 100644
--- a/content/browser/android/browser_jni_registrar.cc
+++ b/content/browser/android/browser_jni_registrar.cc
@@ -24,7 +24,7 @@
 #include "content/browser/android/web_contents_observer_proxy.h"
 #include "content/browser/device_sensors/sensor_manager_android.h"
 #include "content/browser/frame_host/navigation_controller_android.h"
-#include "content/browser/media/session/media_session_delegate_android.h"
+#include "content/browser/media/session/audio_focus_delegate_android.h"
 #include "content/browser/memory/memory_monitor_android.h"
 #include "content/browser/renderer_host/ime_adapter_android.h"
 #include "content/browser/speech/speech_recognizer_impl_android.h"
@@ -35,11 +35,11 @@
 namespace {
 base::android::RegistrationMethod kContentRegisteredMethods[] = {
     {"AppWebMessagePortService", content::RegisterAppWebMessagePortService},
+    {"AudioFocusDelegate", content::AudioFocusDelegateAndroid::Register},
     {"BrowserStartupController", content::RegisterBrowserStartupController},
     {"ChildProcessLauncher", content::RegisterChildProcessLauncher},
     {"ContentVideoView", content::ContentVideoView::RegisterContentVideoView},
     {"CoreImpl", mojo::android::RegisterCoreImpl},
-    {"MediaSessionDelegate", content::MediaSessionDelegateAndroid::Register},
     {"MemoryMonitorAndroid", content::MemoryMonitorAndroid::Register},
     {"BackgroundSyncNetworkObserverAndroid",
      content::BackgroundSyncNetworkObserverAndroid::Observer::
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 8d2e243..0e91801 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -164,7 +164,6 @@
   ARH_CREATED_STREAM_WITHOUT_AUTHORIZATION = 140,
   MDDH_INVALID_DEVICE_TYPE_REQUEST = 141,
   MDDH_UNAUTHORIZED_ORIGIN = 142,
-  NMF_INVALID_ID_CLOSE = 143,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/media/session/audio_focus_delegate.h b/content/browser/media/session/audio_focus_delegate.h
new file mode 100644
index 0000000..064fa9b
--- /dev/null
+++ b/content/browser/media/session/audio_focus_delegate.h
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_H_
+#define CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_H_
+
+#include "content/browser/media/session/audio_focus_manager.h"
+#include "content/browser/media/session/media_session.h"
+
+namespace content {
+
+// AudioFocusDelegate is an interface abstracting audio focus handling for the
+// MediaSession class.
+class AudioFocusDelegate {
+ public:
+  // Factory method returning an implementation of AudioFocusDelegate.
+  static std::unique_ptr<AudioFocusDelegate> Create(
+      MediaSession* media_session);
+
+  virtual ~AudioFocusDelegate() = default;
+
+  virtual bool RequestAudioFocus(
+      AudioFocusManager::AudioFocusType audio_focus_type) = 0;
+  virtual void AbandonAudioFocus() = 0;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_H_
diff --git a/content/browser/media/session/audio_focus_delegate_android.cc b/content/browser/media/session/audio_focus_delegate_android.cc
new file mode 100644
index 0000000..78d1403
--- /dev/null
+++ b/content/browser/media/session/audio_focus_delegate_android.cc
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/audio_focus_delegate_android.h"
+
+#include "base/android/context_utils.h"
+#include "base/android/jni_android.h"
+#include "jni/AudioFocusDelegate_jni.h"
+
+using base::android::JavaParamRef;
+
+namespace content {
+
+// static
+bool AudioFocusDelegateAndroid::Register(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+AudioFocusDelegateAndroid::AudioFocusDelegateAndroid(
+    MediaSession* media_session)
+    : media_session_(media_session) {}
+
+AudioFocusDelegateAndroid::~AudioFocusDelegateAndroid() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  DCHECK(env);
+  Java_AudioFocusDelegate_tearDown(env, j_media_session_delegate_);
+}
+
+void AudioFocusDelegateAndroid::Initialize() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  DCHECK(env);
+  j_media_session_delegate_.Reset(Java_AudioFocusDelegate_create(
+      env, base::android::GetApplicationContext(),
+      reinterpret_cast<intptr_t>(this)));
+}
+
+bool AudioFocusDelegateAndroid::RequestAudioFocus(
+    AudioFocusManager::AudioFocusType audio_focus_type) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  DCHECK(env);
+  return Java_AudioFocusDelegate_requestAudioFocus(
+      env, j_media_session_delegate_,
+      audio_focus_type ==
+          AudioFocusManager::AudioFocusType::GainTransientMayDuck);
+}
+
+void AudioFocusDelegateAndroid::AbandonAudioFocus() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  DCHECK(env);
+  Java_AudioFocusDelegate_abandonAudioFocus(env, j_media_session_delegate_);
+}
+
+void AudioFocusDelegateAndroid::OnSuspend(JNIEnv*,
+                                          const JavaParamRef<jobject>&,
+                                          jboolean temporary) {
+  // TODO(mlamouri): this check makes it so that if a MediaSession is paused and
+  // then loses audio focus, it will still stay in the Suspended state.
+  // See https://crbug.com/539998
+  if (!media_session_->IsActive())
+    return;
+
+  if (temporary) {
+    media_session_->Suspend(MediaSession::SuspendType::SYSTEM);
+  } else {
+    media_session_->Stop(MediaSession::SuspendType::SYSTEM);
+  }
+}
+
+void AudioFocusDelegateAndroid::OnResume(JNIEnv*,
+                                         const JavaParamRef<jobject>&) {
+  if (!media_session_->IsReallySuspended())
+    return;
+
+  media_session_->Resume(MediaSession::SuspendType::SYSTEM);
+}
+
+void AudioFocusDelegateAndroid::OnStartDucking(JNIEnv*, jobject) {
+  media_session_->StartDucking();
+}
+
+void AudioFocusDelegateAndroid::OnStopDucking(JNIEnv*, jobject) {
+  media_session_->StopDucking();
+}
+
+void AudioFocusDelegateAndroid::RecordSessionDuck(
+    JNIEnv*,
+    const JavaParamRef<jobject>&) {
+  media_session_->RecordSessionDuck();
+}
+
+// static
+std::unique_ptr<AudioFocusDelegate> AudioFocusDelegate::Create(
+    MediaSession* media_session) {
+  AudioFocusDelegateAndroid* delegate =
+      new AudioFocusDelegateAndroid(media_session);
+  delegate->Initialize();
+  return std::unique_ptr<AudioFocusDelegate>(delegate);
+}
+
+}  // namespace content
diff --git a/content/browser/media/session/media_session_delegate_android.h b/content/browser/media/session/audio_focus_delegate_android.h
similarity index 73%
rename from content/browser/media/session/media_session_delegate_android.h
rename to content/browser/media/session/audio_focus_delegate_android.h
index f498dd1..8aafde541 100644
--- a/content/browser/media/session/media_session_delegate_android.h
+++ b/content/browser/media/session/audio_focus_delegate_android.h
@@ -2,24 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_ANDROID_H_
-#define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_ANDROID_H_
+#ifndef CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_ANDROID_H_
+#define CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_ANDROID_H_
 
 #include <jni.h>
 
 #include "base/android/scoped_java_ref.h"
-#include "content/browser/media/session/media_session_delegate.h"
+#include "content/browser/media/session/audio_focus_delegate.h"
 
 namespace content {
 
-// MediaSessionDelegateAndroid handles the audio focus at a system level on
+// AudioFocusDelegateAndroid handles the audio focus at a system level on
 // Android. It is also proxying the JNI calls.
-class MediaSessionDelegateAndroid : public MediaSessionDelegate {
+class AudioFocusDelegateAndroid : public AudioFocusDelegate {
  public:
   static bool Register(JNIEnv* env);
 
-  explicit MediaSessionDelegateAndroid(MediaSession* media_session);
-  ~MediaSessionDelegateAndroid() override;
+  explicit AudioFocusDelegateAndroid(MediaSession* media_session);
+  ~AudioFocusDelegateAndroid() override;
 
   void Initialize();
 
@@ -48,16 +48,16 @@
   // Record when the Android system requests the MediaSession to duck.
   // Called by Java through JNI.
   void RecordSessionDuck(JNIEnv* env,
-                         const base::android::JavaParamRef<jobject> &obj);
+                         const base::android::JavaParamRef<jobject>& obj);
 
  private:
   // Weak pointer because |this| is owned by |media_session_|.
   MediaSession* media_session_;
   base::android::ScopedJavaGlobalRef<jobject> j_media_session_delegate_;
 
-  DISALLOW_COPY_AND_ASSIGN(MediaSessionDelegateAndroid);
+  DISALLOW_COPY_AND_ASSIGN(AudioFocusDelegateAndroid);
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_ANDROID_H_
+#endif  // CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_DELEGATE_ANDROID_H_
diff --git a/content/browser/media/session/media_session_delegate_android_browsertest.cc b/content/browser/media/session/audio_focus_delegate_android_browsertest.cc
similarity index 80%
rename from content/browser/media/session/media_session_delegate_android_browsertest.cc
rename to content/browser/media/session/audio_focus_delegate_android_browsertest.cc
index e2d00a3..efdb3feb 100644
--- a/content/browser/media/session/media_session_delegate_android_browsertest.cc
+++ b/content/browser/media/session/audio_focus_delegate_android_browsertest.cc
@@ -10,14 +10,14 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/media/session/media_session.h"
-#include "content/browser/media/session/mock_media_session_observer.h"
+#include "content/browser/media/session/mock_media_session_player_observer.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/shell/browser/shell.h"
 #include "media/base/media_content_type.h"
 
 namespace content {
 
-class MediaSessionDelegateAndroidBrowserTest : public ContentBrowserTest {};
+class AudioFocusDelegateAndroidBrowserTest : public ContentBrowserTest {};
 
 // MAYBE_OnAudioFocusChangeAfterDtorCrash will hit a DCHECK before the crash, it
 // is the only way found to actually reproduce the crash so as a result, the
@@ -31,10 +31,10 @@
   DISABLED_OnAudioFocusChangeAfterDtorCrash
 #endif
 
-IN_PROC_BROWSER_TEST_F(MediaSessionDelegateAndroidBrowserTest,
+IN_PROC_BROWSER_TEST_F(AudioFocusDelegateAndroidBrowserTest,
                        MAYBE_OnAudioFocusChangeAfterDtorCrash) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
   MediaSession* media_session = MediaSession::Get(shell()->web_contents());
   ASSERT_TRUE(media_session);
@@ -43,14 +43,14 @@
   MediaSession* other_media_session = MediaSession::Get(other_web_contents);
   ASSERT_TRUE(other_media_session);
 
-  media_session_observer->StartNewPlayer();
-  media_session->AddPlayer(media_session_observer.get(), 0,
+  player_observer->StartNewPlayer();
+  media_session->AddPlayer(player_observer.get(), 0,
                            media::MediaContentType::Persistent);
   EXPECT_TRUE(media_session->IsActive());
   EXPECT_FALSE(other_media_session->IsActive());
 
-  media_session_observer->StartNewPlayer();
-  other_media_session->AddPlayer(media_session_observer.get(), 1,
+  player_observer->StartNewPlayer();
+  other_media_session->AddPlayer(player_observer.get(), 1,
                                  media::MediaContentType::Persistent);
   EXPECT_TRUE(media_session->IsActive());
   EXPECT_TRUE(other_media_session->IsActive());
diff --git a/content/browser/media/session/audio_focus_delegate_default.cc b/content/browser/media/session/audio_focus_delegate_default.cc
new file mode 100644
index 0000000..f36887f
--- /dev/null
+++ b/content/browser/media/session/audio_focus_delegate_default.cc
@@ -0,0 +1,65 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/audio_focus_delegate.h"
+
+#include "base/command_line.h"
+#include "content/browser/media/session/audio_focus_manager.h"
+#include "media/base/media_switches.h"
+
+namespace content {
+
+using AudioFocusType = AudioFocusManager::AudioFocusType;
+
+namespace {
+
+// AudioFocusDelegateDefault is the default implementation of
+// AudioFocusDelegate which only handles audio focus between WebContents.
+class AudioFocusDelegateDefault : public AudioFocusDelegate {
+ public:
+  explicit AudioFocusDelegateDefault(MediaSession* media_session);
+  ~AudioFocusDelegateDefault() override;
+
+  // AudioFocusDelegate implementation.
+  bool RequestAudioFocus(
+      AudioFocusManager::AudioFocusType audio_focus_type) override;
+  void AbandonAudioFocus() override;
+
+ private:
+  // Weak pointer because |this| is owned by |media_session_|.
+  MediaSession* media_session_;
+};
+
+}  // anonymous namespace
+
+AudioFocusDelegateDefault::AudioFocusDelegateDefault(
+    MediaSession* media_session)
+    : media_session_(media_session) {}
+
+AudioFocusDelegateDefault::~AudioFocusDelegateDefault() = default;
+
+bool AudioFocusDelegateDefault::RequestAudioFocus(
+    AudioFocusManager::AudioFocusType audio_focus_type) {
+  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kEnableDefaultMediaSession)) {
+    return true;
+  }
+
+  AudioFocusManager::GetInstance()->RequestAudioFocus(media_session_,
+                                                      audio_focus_type);
+  return true;
+}
+
+void AudioFocusDelegateDefault::AbandonAudioFocus() {
+  AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_);
+}
+
+// static
+std::unique_ptr<AudioFocusDelegate> AudioFocusDelegate::Create(
+    MediaSession* media_session) {
+  return std::unique_ptr<AudioFocusDelegate>(
+      new AudioFocusDelegateDefault(media_session));
+}
+
+}  // namespace content
diff --git a/content/browser/media/session/media_session_delegate_default_browsertest.cc b/content/browser/media/session/audio_focus_delegate_default_browsertest.cc
similarity index 84%
rename from content/browser/media/session/media_session_delegate_default_browsertest.cc
rename to content/browser/media/session/audio_focus_delegate_default_browsertest.cc
index 921c084..8456748e 100644
--- a/content/browser/media/session/media_session_delegate_default_browsertest.cc
+++ b/content/browser/media/session/audio_focus_delegate_default_browsertest.cc
@@ -4,7 +4,7 @@
 
 #include "base/command_line.h"
 #include "content/browser/media/session/media_session.h"
-#include "content/browser/media/session/mock_media_session_observer.h"
+#include "content/browser/media/session/mock_media_session_player_observer.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/shell/browser/shell.h"
 #include "media/base/media_content_type.h"
@@ -19,8 +19,8 @@
   }
 
   void Run(WebContents* start_contents, WebContents* interrupt_contents) {
-    std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-        new MockMediaSessionObserver);
+    std::unique_ptr<MockMediaSessionPlayerObserver>
+        player_observer(new MockMediaSessionPlayerObserver);
 
     MediaSession* media_session = MediaSession::Get(start_contents);
     ASSERT_TRUE(media_session);
@@ -28,14 +28,14 @@
     MediaSession* other_media_session = MediaSession::Get(interrupt_contents);
     ASSERT_TRUE(other_media_session);
 
-    media_session_observer->StartNewPlayer();
-    media_session->AddPlayer(media_session_observer.get(), 0,
+    player_observer->StartNewPlayer();
+    media_session->AddPlayer(player_observer.get(), 0,
                              media::MediaContentType::Persistent);
     EXPECT_TRUE(media_session->IsActive());
     EXPECT_FALSE(other_media_session->IsActive());
 
-    media_session_observer->StartNewPlayer();
-    other_media_session->AddPlayer(media_session_observer.get(), 1,
+    player_observer->StartNewPlayer();
+    other_media_session->AddPlayer(player_observer.get(), 1,
                                    media::MediaContentType::Persistent);
     EXPECT_FALSE(media_session->IsActive());
     EXPECT_TRUE(other_media_session->IsActive());
diff --git a/content/browser/media/session/audio_focus_manager_unittest.cc b/content/browser/media/session/audio_focus_manager_unittest.cc
index df66e86..d853a2cb 100644
--- a/content/browser/media/session/audio_focus_manager_unittest.cc
+++ b/content/browser/media/session/audio_focus_manager_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "base/command_line.h"
 #include "content/browser/media/session/media_session.h"
-#include "content/browser/media/session/media_session_observer.h"
+#include "content/browser/media/session/media_session_player_observer.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread.h"
@@ -18,7 +18,7 @@
 
 namespace {
 
-class MockMediaSessionObserver : public MediaSessionObserver {
+class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
  public:
   void OnSuspend(int player_id) override {}
   void OnResume(int player_id) override {}
@@ -41,7 +41,7 @@
     rph_factory_.reset(new MockRenderProcessHostFactory());
     SiteInstanceImpl::set_render_process_host_factory(rph_factory_.get());
     browser_context_.reset(new TestBrowserContext());
-    pepper_observer_.reset(new MockMediaSessionObserver());
+    pepper_observer_.reset(new MockMediaSessionPlayerObserver());
   }
 
   void TearDown() override {
@@ -97,7 +97,7 @@
         SiteInstance::SiteInstance::Create(browser_context_.get()));
   }
 
-  std::unique_ptr<MediaSessionObserver> pepper_observer_;
+  std::unique_ptr<MediaSessionPlayerObserver> pepper_observer_;
 
  private:
   base::MessageLoopForUI message_loop_;
diff --git a/content/browser/media/session/media_session.cc b/content/browser/media/session/media_session.cc
index aa165b9..f4d3c09 100644
--- a/content/browser/media/session/media_session.cc
+++ b/content/browser/media/session/media_session.cc
@@ -4,8 +4,8 @@
 
 #include "content/browser/media/session/media_session.h"
 
-#include "content/browser/media/session/media_session_delegate.h"
-#include "content/browser/media/session/media_session_observer.h"
+#include "content/browser/media/session/audio_focus_delegate.h"
+#include "content/browser/media/session/media_session_player_observer.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -25,11 +25,10 @@
 
 DEFINE_WEB_CONTENTS_USER_DATA_KEY(MediaSession);
 
-MediaSession::PlayerIdentifier::PlayerIdentifier(MediaSessionObserver* observer,
-                                                 int player_id)
-    : observer(observer),
-      player_id(player_id) {
-}
+MediaSession::PlayerIdentifier::PlayerIdentifier(
+    MediaSessionPlayerObserver* observer,
+    int player_id)
+    : observer(observer), player_id(player_id) {}
 
 bool MediaSession::PlayerIdentifier::operator==(
     const PlayerIdentifier& other) const {
@@ -38,7 +37,7 @@
 
 size_t MediaSession::PlayerIdentifier::Hash::operator()(
     const PlayerIdentifier& player_identifier) const {
-  size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionObserver*>()(
+  size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionPlayerObserver*>()(
       player_identifier.observer);
   hash += BASE_HASH_NAMESPACE::hash<int>()(player_identifier.player_id);
   return hash;
@@ -79,7 +78,7 @@
       ->OnMediaSessionMetadataChanged();
 }
 
-bool MediaSession::AddPlayer(MediaSessionObserver* observer,
+bool MediaSession::AddPlayer(MediaSessionPlayerObserver* observer,
                              int player_id,
                              media::MediaContentType media_content_type) {
   if (media_content_type == media::MediaContentType::Pepper)
@@ -126,7 +125,7 @@
   return true;
 }
 
-void MediaSession::RemovePlayer(MediaSessionObserver* observer,
+void MediaSession::RemovePlayer(MediaSessionPlayerObserver* observer,
                                 int player_id) {
   auto it = players_.find(PlayerIdentifier(observer, player_id));
   if (it != players_.end())
@@ -139,7 +138,7 @@
   AbandonSystemAudioFocusIfNeeded();
 }
 
-void MediaSession::RemovePlayers(MediaSessionObserver* observer) {
+void MediaSession::RemovePlayers(MediaSessionPlayerObserver* observer) {
   for (auto it = players_.begin(); it != players_.end(); ) {
     if (it->observer == observer)
       players_.erase(it++);
@@ -162,7 +161,7 @@
       MediaSessionSuspendedSource::SystemTransientDuck);
 }
 
-void MediaSession::OnPlayerPaused(MediaSessionObserver* observer,
+void MediaSession::OnPlayerPaused(MediaSessionPlayerObserver* observer,
                                   int player_id) {
   // If a playback is completed, BrowserMediaPlayerManager will call
   // OnPlayerPaused() after RemovePlayer(). This is a workaround.
@@ -285,7 +284,7 @@
 }
 
 void MediaSession::SetDelegateForTests(
-    std::unique_ptr<MediaSessionDelegate> delegate) {
+    std::unique_ptr<AudioFocusDelegate> delegate) {
   delegate_ = std::move(delegate);
 }
 
@@ -377,7 +376,7 @@
       is_ducking_(false) {}
 
 void MediaSession::Initialize() {
-  delegate_ = MediaSessionDelegate::Create(this);
+  delegate_ = AudioFocusDelegate::Create(this);
 }
 
 bool MediaSession::RequestSystemAudioFocus(
@@ -426,7 +425,7 @@
   }
 }
 
-bool MediaSession::AddPepperPlayer(MediaSessionObserver* observer,
+bool MediaSession::AddPepperPlayer(MediaSessionPlayerObserver* observer,
                                    int player_id) {
   bool success = RequestSystemAudioFocus(
       AudioFocusManager::AudioFocusType::Gain);
diff --git a/content/browser/media/session/media_session.h b/content/browser/media/session/media_session.h
index ba8f999..9239c785 100644
--- a/content/browser/media/session/media_session.h
+++ b/content/browser/media/session/media_session.h
@@ -26,9 +26,9 @@
 
 namespace content {
 
+class AudioFocusDelegate;
 class AudioFocusManagerTest;
-class MediaSessionDelegate;
-class MediaSessionObserver;
+class MediaSessionPlayerObserver;
 class MediaSessionStateObserver;
 class MediaSessionVisibilityBrowserTest;
 
@@ -77,18 +77,18 @@
   // Adds the given player to the current media session. Returns whether the
   // player was successfully added. If it returns false, AddPlayer() should be
   // called again later.
-  CONTENT_EXPORT bool AddPlayer(MediaSessionObserver* observer,
+  CONTENT_EXPORT bool AddPlayer(MediaSessionPlayerObserver* observer,
                                 int player_id,
                                 media::MediaContentType media_content_type);
 
   // Removes the given player from the current media session. Abandons audio
   // focus if that was the last player in the session.
-  CONTENT_EXPORT void RemovePlayer(MediaSessionObserver* observer,
+  CONTENT_EXPORT void RemovePlayer(MediaSessionPlayerObserver* observer,
                                    int player_id);
 
   // Removes all the players associated with |observer|. Abandons audio focus if
   // these were the last players in the session.
-  CONTENT_EXPORT void RemovePlayers(MediaSessionObserver* observer);
+  CONTENT_EXPORT void RemovePlayers(MediaSessionPlayerObserver* observer);
 
   // Record that the session was ducked.
   void RecordSessionDuck();
@@ -96,7 +96,7 @@
   // Called when a player is paused in the content.
   // If the paused player is the last player, we suspend the MediaSession.
   // Otherwise, the paused player will be removed from the MediaSession.
-  CONTENT_EXPORT void OnPlayerPaused(MediaSessionObserver* observer,
+  CONTENT_EXPORT void OnPlayerPaused(MediaSessionPlayerObserver* observer,
                                      int player_id);
 
   // Resume the media session.
@@ -156,14 +156,14 @@
   friend class content::MediaSessionStateObserver;
 
   CONTENT_EXPORT void SetDelegateForTests(
-      std::unique_ptr<MediaSessionDelegate> delegate);
+      std::unique_ptr<AudioFocusDelegate> delegate);
   CONTENT_EXPORT bool IsActiveForTest() const;
   CONTENT_EXPORT void RemoveAllPlayersForTest();
   CONTENT_EXPORT MediaSessionUmaHelper* uma_helper_for_test();
 
   // Representation of a player for the MediaSession.
   struct PlayerIdentifier {
-    PlayerIdentifier(MediaSessionObserver* observer, int player_id);
+    PlayerIdentifier(MediaSessionPlayerObserver* observer, int player_id);
     PlayerIdentifier(const PlayerIdentifier&) = default;
 
     void operator=(const PlayerIdentifier&) = delete;
@@ -174,7 +174,7 @@
       size_t operator()(const PlayerIdentifier& player_identifier) const;
     };
 
-    MediaSessionObserver* observer;
+    MediaSessionPlayerObserver* observer;
     int player_id;
   };
   using PlayersMap = base::hash_set<PlayerIdentifier, PlayerIdentifier::Hash>;
@@ -188,7 +188,7 @@
                                         State new_state);
   CONTENT_EXPORT void OnResumeInternal(SuspendType suspend_type);
 
-  // Requests audio focus to the MediaSessionDelegate.
+  // Requests audio focus to the AudioFocusDelegate.
   // Returns whether the request was granted.
   CONTENT_EXPORT bool RequestSystemAudioFocus(
       AudioFocusManager::AudioFocusType audio_focus_type);
@@ -216,10 +216,10 @@
   RegisterMediaSessionStateChangedCallbackForTest(
       const StateChangedCallback& cb);
 
-  CONTENT_EXPORT bool AddPepperPlayer(MediaSessionObserver* observer,
+  CONTENT_EXPORT bool AddPepperPlayer(MediaSessionPlayerObserver* observer,
                                       int player_id);
 
-  std::unique_ptr<MediaSessionDelegate> delegate_;
+  std::unique_ptr<AudioFocusDelegate> delegate_;
   PlayersMap players_;
   PlayersMap pepper_players_;
 
diff --git a/content/browser/media/session/media_session_browsertest.cc b/content/browser/media/session/media_session_browsertest.cc
index 959bead1..5f79eeb 100644
--- a/content/browser/media/session/media_session_browsertest.cc
+++ b/content/browser/media/session/media_session_browsertest.cc
@@ -13,8 +13,8 @@
 #include "base/metrics/histogram_samples.h"
 #include "base/test/histogram_tester.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "content/browser/media/session/media_session_delegate.h"
-#include "content/browser/media/session/mock_media_session_observer.h"
+#include "content/browser/media/session/audio_focus_delegate.h"
+#include "content/browser/media/session/mock_media_session_player_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/content_browser_test.h"
@@ -25,10 +25,10 @@
 using content::WebContents;
 using content::WebContentsObserver;
 using content::MediaSession;
-using content::MediaSessionDelegate;
-using content::MediaSessionObserver;
+using content::AudioFocusDelegate;
+using content::MediaSessionPlayerObserver;
 using content::MediaSessionUmaHelper;
-using content::MockMediaSessionObserver;
+using content::MockMediaSessionPlayerObserver;
 
 using ::testing::Expectation;
 
@@ -37,7 +37,7 @@
 const double kDefaultVolumeMultiplier = 1.0;
 const double kDuckingVolumeMultiplier = 0.2;
 
-class MockMediaSessionDelegate : public MediaSessionDelegate {
+class MockAudioFocusDelegate : public AudioFocusDelegate {
  public:
   bool RequestAudioFocus(content::AudioFocusManager::AudioFocusType) override {
     return true;
@@ -69,7 +69,7 @@
         new MockWebContentsObserver(shell()->web_contents()));
     media_session_ = MediaSession::Get(shell()->web_contents());
     media_session_->SetDelegateForTests(
-        std::unique_ptr<MediaSessionDelegate>(new MockMediaSessionDelegate()));
+        std::unique_ptr<AudioFocusDelegate>(new MockAudioFocusDelegate()));
     ASSERT_TRUE(media_session_);
   }
 
@@ -82,32 +82,37 @@
     ContentBrowserTest::TearDownOnMainThread();
   }
 
-  void StartNewPlayer(MockMediaSessionObserver* media_session_observer,
-                      media::MediaContentType media_content_type) {
-    bool result =
-        AddPlayer(media_session_observer,
-                  media_session_observer->StartNewPlayer(), media_content_type);
+  void StartNewPlayer(
+      MockMediaSessionPlayerObserver* player_observer,
+      media::MediaContentType media_content_type) {
+    bool result = AddPlayer(player_observer,
+                            player_observer->StartNewPlayer(),
+                            media_content_type);
     EXPECT_TRUE(result);
   }
 
-  bool AddPlayer(MockMediaSessionObserver* media_session_observer,
+  bool AddPlayer(MockMediaSessionPlayerObserver* player_observer,
                  int player_id,
                  media::MediaContentType type) {
-    return media_session_->AddPlayer(media_session_observer, player_id, type);
+    return media_session_->AddPlayer(player_observer, player_id,
+                                     type);
   }
 
-  void RemovePlayer(MockMediaSessionObserver* media_session_observer,
-                    int player_id) {
-    media_session_->RemovePlayer(media_session_observer, player_id);
+  void RemovePlayer(
+      MockMediaSessionPlayerObserver* player_observer,
+      int player_id) {
+    media_session_->RemovePlayer(player_observer, player_id);
   }
 
-  void RemovePlayers(MockMediaSessionObserver* media_session_observer) {
-    media_session_->RemovePlayers(media_session_observer);
+  void RemovePlayers(
+      MockMediaSessionPlayerObserver* player_observer) {
+    media_session_->RemovePlayers(player_observer);
   }
 
-  void OnPlayerPaused(MockMediaSessionObserver* media_session_observer,
-                      int player_id) {
-    media_session_->OnPlayerPaused(media_session_observer, player_id);
+  void OnPlayerPaused(
+      MockMediaSessionPlayerObserver* player_observer,
+      int player_id) {
+    media_session_->OnPlayerPaused(player_observer, player_id);
   }
 
   bool HasAudioFocus() { return media_session_->IsActiveForTest(); }
@@ -168,171 +173,170 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        PlayersFromSameObserverDoNotStopEachOtherInSameSession) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_TRUE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
-  EXPECT_TRUE(media_session_observer->IsPlaying(2));
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        PlayersFromManyObserverDoNotStopEachOtherInSameSession) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_1(
-      new MockMediaSessionObserver);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_2(
-      new MockMediaSessionObserver);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_3(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_1(new MockMediaSessionPlayerObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_2(new MockMediaSessionPlayerObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_3(new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer_1.get(),
+  StartNewPlayer(player_observer_1.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_2.get(),
+  StartNewPlayer(player_observer_2.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_3.get(),
+  StartNewPlayer(player_observer_3.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_TRUE(media_session_observer_1->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer_2->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer_3->IsPlaying(0));
+  EXPECT_TRUE(player_observer_1->IsPlaying(0));
+  EXPECT_TRUE(player_observer_2->IsPlaying(0));
+  EXPECT_TRUE(player_observer_3->IsPlaying(0));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        SuspendedMediaSessionStopsPlayers) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
 
-  EXPECT_FALSE(media_session_observer->IsPlaying(0));
-  EXPECT_FALSE(media_session_observer->IsPlaying(1));
-  EXPECT_FALSE(media_session_observer->IsPlaying(2));
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+  EXPECT_FALSE(player_observer->IsPlaying(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        ResumedMediaSessionRestartsPlayers) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
   SystemResume();
 
-  EXPECT_TRUE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
-  EXPECT_TRUE(media_session_observer->IsPlaying(2));
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        StartedPlayerOnSuspendedSessionPlaysAlone) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_TRUE(media_session_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(0));
 
   SystemSuspend(true);
 
-  EXPECT_FALSE(media_session_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(0));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_FALSE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_FALSE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
-  EXPECT_TRUE(media_session_observer->IsPlaying(2));
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        InitialVolumeMultiplier) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_EQ(kDefaultVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(0));
+            player_observer->GetVolumeMultiplier(0));
   EXPECT_EQ(kDefaultVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(1));
-
+            player_observer->GetVolumeMultiplier(1));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        StartDuckingReducesVolumeMultiplier) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemStartDucking();
 
   EXPECT_EQ(kDuckingVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(0));
+            player_observer->GetVolumeMultiplier(0));
   EXPECT_EQ(kDuckingVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(1));
+            player_observer->GetVolumeMultiplier(1));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_EQ(kDuckingVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(2));
+            player_observer->GetVolumeMultiplier(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        StopDuckingRecoversVolumeMultiplier) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemStartDucking();
   SystemStopDucking();
 
   EXPECT_EQ(kDefaultVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(0));
+            player_observer->GetVolumeMultiplier(0));
   EXPECT_EQ(kDefaultVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(1));
+            player_observer->GetVolumeMultiplier(1));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_EQ(kDefaultVolumeMultiplier,
-            media_session_observer->GetVolumeMultiplier(2));
+            player_observer->GetVolumeMultiplier(2));
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, AudioFocusInitialState) {
@@ -340,20 +344,20 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, StartPlayerGivesFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_TRUE(HasAudioFocus());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, SuspendGivesAwayAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
@@ -362,10 +366,10 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, StopGivesAwayAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   media_session_->Stop(MediaSession::SuspendType::UI);
@@ -374,10 +378,10 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ResumeGivesBackAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
@@ -388,198 +392,198 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        RemovingLastPlayerDropsAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
   EXPECT_TRUE(HasAudioFocus());
-  RemovePlayer(media_session_observer.get(), 1);
+  RemovePlayer(player_observer.get(), 1);
   EXPECT_TRUE(HasAudioFocus());
-  RemovePlayer(media_session_observer.get(), 2);
+  RemovePlayer(player_observer.get(), 2);
   EXPECT_FALSE(HasAudioFocus());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        RemovingLastPlayerFromManyObserversDropsAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_1(
-      new MockMediaSessionObserver);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_2(
-      new MockMediaSessionObserver);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_3(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_1(new MockMediaSessionPlayerObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_2(new MockMediaSessionPlayerObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_3(new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer_1.get(),
+  StartNewPlayer(player_observer_1.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_2.get(),
+  StartNewPlayer(player_observer_2.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_3.get(),
+  StartNewPlayer(player_observer_3.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayer(media_session_observer_1.get(), 0);
+  RemovePlayer(player_observer_1.get(), 0);
   EXPECT_TRUE(HasAudioFocus());
-  RemovePlayer(media_session_observer_2.get(), 0);
+  RemovePlayer(player_observer_2.get(), 0);
   EXPECT_TRUE(HasAudioFocus());
-  RemovePlayer(media_session_observer_3.get(), 0);
+  RemovePlayer(player_observer_3.get(), 0);
   EXPECT_FALSE(HasAudioFocus());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        RemovingAllPlayersFromObserversDropsAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_1(
-      new MockMediaSessionObserver);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer_2(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_1(new MockMediaSessionPlayerObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver>
+      player_observer_2(new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer_1.get(),
+  StartNewPlayer(player_observer_1.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_1.get(),
+  StartNewPlayer(player_observer_1.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_2.get(),
+  StartNewPlayer(player_observer_2.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer_2.get(),
+  StartNewPlayer(player_observer_2.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayers(media_session_observer_1.get());
+  RemovePlayers(player_observer_1.get());
   EXPECT_TRUE(HasAudioFocus());
-  RemovePlayers(media_session_observer_2.get());
+  RemovePlayers(player_observer_2.get());
   EXPECT_FALSE(HasAudioFocus());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ResumePlayGivesAudioFocus) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
   EXPECT_FALSE(HasAudioFocus());
 
-  EXPECT_TRUE(AddPlayer(media_session_observer.get(), 0,
+  EXPECT_TRUE(AddPlayer(player_observer.get(), 0,
                         media::MediaContentType::Persistent));
   EXPECT_TRUE(HasAudioFocus());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        ResumeSuspendAreSentOnlyOncePerPlayers) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  EXPECT_EQ(0, media_session_observer->received_suspend_calls());
-  EXPECT_EQ(0, media_session_observer->received_resume_calls());
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
 
   SystemSuspend(true);
-  EXPECT_EQ(3, media_session_observer->received_suspend_calls());
+  EXPECT_EQ(3, player_observer->received_suspend_calls());
 
   SystemResume();
-  EXPECT_EQ(3, media_session_observer->received_resume_calls());
+  EXPECT_EQ(3, player_observer->received_resume_calls());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        ResumeSuspendAreSentOnlyOncePerPlayersAddedTwice) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   // Adding the three players above again.
-  EXPECT_TRUE(AddPlayer(media_session_observer.get(), 0,
+  EXPECT_TRUE(AddPlayer(player_observer.get(), 0,
                         media::MediaContentType::Persistent));
-  EXPECT_TRUE(AddPlayer(media_session_observer.get(), 1,
+  EXPECT_TRUE(AddPlayer(player_observer.get(), 1,
                         media::MediaContentType::Persistent));
-  EXPECT_TRUE(AddPlayer(media_session_observer.get(), 2,
+  EXPECT_TRUE(AddPlayer(player_observer.get(), 2,
                         media::MediaContentType::Persistent));
 
-  EXPECT_EQ(0, media_session_observer->received_suspend_calls());
-  EXPECT_EQ(0, media_session_observer->received_resume_calls());
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
 
   SystemSuspend(true);
-  EXPECT_EQ(3, media_session_observer->received_suspend_calls());
+  EXPECT_EQ(3, player_observer->received_suspend_calls());
 
   SystemResume();
-  EXPECT_EQ(3, media_session_observer->received_resume_calls());
+  EXPECT_EQ(3, player_observer->received_resume_calls());
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        RemovingTheSamePlayerTwiceIsANoop) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayer(media_session_observer.get(), 0);
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, AudioFocusType) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
   // Starting a player with a given type should set the session to that type.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::GainTransientMayDuck,
             GetSessionAudioFocusType());
 
   // Adding a player of the same type should have no effect on the type.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::GainTransientMayDuck,
             GetSessionAudioFocusType());
 
   // Adding a player of Content type should override the current type.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::Gain,
             GetSessionAudioFocusType());
 
   // Adding a player of the Transient type should have no effect on the type.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::Gain,
             GetSessionAudioFocusType());
 
-  EXPECT_TRUE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
-  EXPECT_TRUE(media_session_observer->IsPlaying(2));
-  EXPECT_TRUE(media_session_observer->IsPlaying(3));
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(player_observer->IsPlaying(2));
+  EXPECT_TRUE(player_observer->IsPlaying(3));
 
   SystemSuspend(true);
 
-  EXPECT_FALSE(media_session_observer->IsPlaying(0));
-  EXPECT_FALSE(media_session_observer->IsPlaying(1));
-  EXPECT_FALSE(media_session_observer->IsPlaying(2));
-  EXPECT_FALSE(media_session_observer->IsPlaying(3));
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+  EXPECT_FALSE(player_observer->IsPlaying(2));
+  EXPECT_FALSE(player_observer->IsPlaying(3));
 
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::Gain,
             GetSessionAudioFocusType());
 
   SystemResume();
 
-  EXPECT_TRUE(media_session_observer->IsPlaying(0));
-  EXPECT_TRUE(media_session_observer->IsPlaying(1));
-  EXPECT_TRUE(media_session_observer->IsPlaying(2));
-  EXPECT_TRUE(media_session_observer->IsPlaying(3));
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(player_observer->IsPlaying(2));
+  EXPECT_TRUE(player_observer->IsPlaying(3));
 
   EXPECT_EQ(content::AudioFocusManager::AudioFocusType::Gain,
             GetSessionAudioFocusType());
@@ -589,11 +593,11 @@
   EXPECT_CALL(*mock_web_contents_observer(),
               MediaSessionStateChanged(true, false));
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
   // Starting a player with a content type should show the media controls.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_TRUE(IsControllable());
@@ -604,11 +608,11 @@
   EXPECT_CALL(*mock_web_contents_observer(),
               MediaSessionStateChanged(false, false));
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
   // Starting a player with a transient type should not show the media controls.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
 
   EXPECT_FALSE(IsControllable());
@@ -622,13 +626,13 @@
               MediaSessionStateChanged(false, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayers(media_session_observer.get());
+  RemovePlayers(player_observer.get());
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsSuspended());
@@ -638,14 +642,14 @@
   EXPECT_CALL(*mock_web_contents_observer(),
               MediaSessionStateChanged(true, false));
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   // Transient player join the session without affecting the controls.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
 
   EXPECT_TRUE(IsControllable());
@@ -660,14 +664,14 @@
               MediaSessionStateChanged(true, false))
       .After(dontShowControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
 
   // The controls are shown when the content player is added.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_TRUE(IsControllable());
@@ -679,17 +683,17 @@
   EXPECT_CALL(*mock_web_contents_observer(),
               MediaSessionStateChanged(true, false));
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
 
   // Removing only content player doesn't hide the controls since the session
   // is still active.
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
 
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsSuspended());
@@ -702,20 +706,20 @@
   EXPECT_CALL(*mock_web_contents_observer(),
               MediaSessionStateChanged(false, true))
       .After(showControls);
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
 
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsSuspended());
 
-  RemovePlayer(media_session_observer.get(), 1);
+  RemovePlayer(player_observer.get(), 1);
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsSuspended());
@@ -729,15 +733,15 @@
               MediaSessionStateChanged(false, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  RemovePlayers(media_session_observer.get());
+  RemovePlayers(player_observer.get());
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsSuspended());
@@ -751,20 +755,20 @@
               MediaSessionStateChanged(true, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
-  OnPlayerPaused(media_session_observer.get(), 0);
+  OnPlayerPaused(player_observer.get(), 0);
 
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsSuspended());
 
-  OnPlayerPaused(media_session_observer.get(), 1);
+  OnPlayerPaused(player_observer.get(), 1);
 
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsSuspended());
@@ -778,10 +782,10 @@
               MediaSessionStateChanged(true, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
@@ -800,10 +804,10 @@
               MediaSessionStateChanged(true, false))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(true);
   SystemResume();
@@ -820,10 +824,10 @@
               MediaSessionStateChanged(false, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(false);
@@ -843,10 +847,10 @@
               MediaSessionStateChanged(false, true))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   media_session_->Stop(MediaSession::SuspendType::UI);
@@ -866,16 +870,16 @@
               MediaSessionStateChanged(false, false))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(true);
 
   // This should reset the session and change it to a transient, so
   // hide the controls.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Transient);
 
   EXPECT_FALSE(IsControllable());
@@ -893,15 +897,15 @@
               MediaSessionStateChanged(true, false))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(true);
 
   // This should reset the session and update the controls.
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   EXPECT_TRUE(IsControllable());
@@ -919,15 +923,15 @@
               MediaSessionStateChanged(true, false))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(true);
 
   // This should resume the session and update the controls.
-  AddPlayer(media_session_observer.get(), 0,
+  AddPlayer(player_observer.get(), 0,
             media::MediaContentType::Persistent);
 
   EXPECT_TRUE(IsControllable());
@@ -942,10 +946,10 @@
               MediaSessionStateChanged(true, true))
       .After(showControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   UISuspend();
 
@@ -964,10 +968,10 @@
               MediaSessionStateChanged(true, false))
       .After(pauseControls);
 
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   UISuspend();
   UIResume();
@@ -978,9 +982,9 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        DontResumeBySystemUISuspendedSessions) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
-  StartNewPlayer(media_session_observer.get(),
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   UISuspend();
@@ -994,9 +998,9 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        AllowUIResumeForSystemSuspend) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
-  StartNewPlayer(media_session_observer.get(),
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
@@ -1009,9 +1013,9 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ResumeSuspendFromUI) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
-  StartNewPlayer(media_session_observer.get(),
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   UISuspend();
@@ -1024,9 +1028,9 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ResumeSuspendFromSystem) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
-  StartNewPlayer(media_session_observer.get(),
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   SystemSuspend(true);
@@ -1039,11 +1043,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, UMA_Suspended_SystemTransient) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(true);
 
@@ -1058,11 +1062,11 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_Suspended_SystemPermantent) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   SystemSuspend(false);
 
@@ -1075,11 +1079,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, UMA_Suspended_UI) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   UISuspend();
 
@@ -1092,11 +1096,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, UMA_Suspended_Multiple) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   UISuspend();
@@ -1119,11 +1123,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, UMA_Suspended_Crossing) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   UISuspend();
@@ -1145,11 +1149,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, UMA_Suspended_Stop) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   media_session_->Stop(MediaSession::SuspendType::UI);
 
@@ -1175,8 +1179,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_SimpleActivation) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1185,7 +1189,7 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   clock->Advance(base::TimeDelta::FromMilliseconds(1000));
@@ -1199,8 +1203,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_ActivationWithUISuspension) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1209,7 +1213,7 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   clock->Advance(base::TimeDelta::FromMilliseconds(1000));
@@ -1229,8 +1233,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_ActivationWithSystemSuspension) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1239,7 +1243,7 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
 
   clock->Advance(base::TimeDelta::FromMilliseconds(1000));
@@ -1259,8 +1263,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_ActivateSuspendedButNotStopped) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1269,7 +1273,7 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   clock->Advance(base::TimeDelta::FromMilliseconds(500));
   SystemSuspend(true);
@@ -1293,8 +1297,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_ActivateSuspendStopTwice) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1303,13 +1307,13 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   clock->Advance(base::TimeDelta::FromMilliseconds(500));
   SystemSuspend(true);
   media_session_->Stop(MediaSession::SuspendType::UI);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   clock->Advance(base::TimeDelta::FromMilliseconds(5000));
   SystemResume();
@@ -1324,8 +1328,8 @@
 
 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
                        UMA_ActiveTime_MultipleActivations) {
-  std::unique_ptr<MockMediaSessionObserver> media_session_observer(
-      new MockMediaSessionObserver);
+  std::unique_ptr<MockMediaSessionPlayerObserver> player_observer(
+      new MockMediaSessionPlayerObserver);
   base::HistogramTester tester;
 
   MediaSessionUmaHelper* media_session_uma_helper = GetMediaSessionUMAHelper();
@@ -1334,12 +1338,12 @@
   media_session_uma_helper->SetClockForTest(
       std::unique_ptr<base::SimpleTestTickClock>(clock));
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   clock->Advance(base::TimeDelta::FromMilliseconds(10000));
-  RemovePlayer(media_session_observer.get(), 0);
+  RemovePlayer(player_observer.get(), 0);
 
-  StartNewPlayer(media_session_observer.get(),
+  StartNewPlayer(player_observer.get(),
                  media::MediaContentType::Persistent);
   clock->Advance(base::TimeDelta::FromMilliseconds(1000));
   media_session_->Stop(MediaSession::SuspendType::UI);
diff --git a/content/browser/media/session/media_session_controller.h b/content/browser/media/session/media_session_controller.h
index ce5af65..4017f53 100644
--- a/content/browser/media/session/media_session_controller.h
+++ b/content/browser/media/session/media_session_controller.h
@@ -7,7 +7,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/time/time.h"
-#include "content/browser/media/session/media_session_observer.h"
+#include "content/browser/media/session/media_session_player_observer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/web_contents_observer.h"
 
@@ -23,8 +23,8 @@
 // Helper class for controlling a single player's MediaSession instance.  Sends
 // browser side MediaSession commands back to a player hosted in the renderer
 // process.
-class CONTENT_EXPORT MediaSessionController :
-    NON_EXPORTED_BASE(public MediaSessionObserver) {
+class CONTENT_EXPORT MediaSessionController
+    : NON_EXPORTED_BASE(public MediaSessionPlayerObserver) {
  public:
   MediaSessionController(const WebContentsObserver::MediaPlayerId& id,
                          MediaWebContentsObserver* media_web_contents_observer);
diff --git a/content/browser/media/session/media_session_controllers_manager.cc b/content/browser/media/session/media_session_controllers_manager.cc
index f0cf9d4..f924c9f 100644
--- a/content/browser/media/session/media_session_controllers_manager.cc
+++ b/content/browser/media/session/media_session_controllers_manager.cc
@@ -7,7 +7,6 @@
 #include "base/command_line.h"
 #include "content/browser/media/session/media_session.h"
 #include "content/browser/media/session/media_session_controller.h"
-#include "content/browser/media/session/media_session_observer.h"
 #include "media/base/media_switches.h"
 
 namespace content {
diff --git a/content/browser/media/session/media_session_delegate.h b/content/browser/media/session/media_session_delegate.h
deleted file mode 100644
index a0ed28b9..0000000
--- a/content/browser/media/session/media_session_delegate.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_H_
-#define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_H_
-
-#include "content/browser/media/session/audio_focus_manager.h"
-#include "content/browser/media/session/media_session.h"
-
-namespace content {
-
-// MediaSessionDelegate is an interface abstracting audio focus handling for the
-// MediaSession class.
-class MediaSessionDelegate {
- public:
-  // Factory method returning an implementation of MediaSessionDelegate.
-  static std::unique_ptr<MediaSessionDelegate> Create(
-      MediaSession* media_session);
-
-  virtual ~MediaSessionDelegate() = default;
-
-  virtual bool RequestAudioFocus(
-      AudioFocusManager::AudioFocusType audio_focus_type) = 0;
-  virtual void AbandonAudioFocus() = 0;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_DELEGATE_H_
diff --git a/content/browser/media/session/media_session_delegate_android.cc b/content/browser/media/session/media_session_delegate_android.cc
deleted file mode 100644
index da826e9..0000000
--- a/content/browser/media/session/media_session_delegate_android.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/media/session/media_session_delegate_android.h"
-
-#include "base/android/context_utils.h"
-#include "base/android/jni_android.h"
-#include "jni/MediaSessionDelegate_jni.h"
-
-using base::android::JavaParamRef;
-
-namespace content {
-
-// static
-bool MediaSessionDelegateAndroid::Register(JNIEnv* env) {
-  return RegisterNativesImpl(env);
-}
-
-MediaSessionDelegateAndroid::MediaSessionDelegateAndroid(
-    MediaSession* media_session)
-    : media_session_(media_session) {
-}
-
-MediaSessionDelegateAndroid::~MediaSessionDelegateAndroid() {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  DCHECK(env);
-  Java_MediaSessionDelegate_tearDown(env, j_media_session_delegate_);
-}
-
-void MediaSessionDelegateAndroid::Initialize() {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  DCHECK(env);
-  j_media_session_delegate_.Reset(Java_MediaSessionDelegate_create(
-      env,
-      base::android::GetApplicationContext(),
-      reinterpret_cast<intptr_t>(this)));
-}
-
-bool MediaSessionDelegateAndroid::RequestAudioFocus(
-    AudioFocusManager::AudioFocusType audio_focus_type) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  DCHECK(env);
-  return Java_MediaSessionDelegate_requestAudioFocus(
-      env, j_media_session_delegate_,
-      audio_focus_type ==
-          AudioFocusManager::AudioFocusType::GainTransientMayDuck);
-}
-
-void MediaSessionDelegateAndroid::AbandonAudioFocus() {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  DCHECK(env);
-  Java_MediaSessionDelegate_abandonAudioFocus(env, j_media_session_delegate_);
-}
-
-void MediaSessionDelegateAndroid::OnSuspend(
-    JNIEnv*, const JavaParamRef<jobject>&, jboolean temporary) {
-  // TODO(mlamouri): this check makes it so that if a MediaSession is paused and
-  // then loses audio focus, it will still stay in the Suspended state.
-  // See https://crbug.com/539998
-  if (!media_session_->IsActive())
-    return;
-
-  if (temporary) {
-    media_session_->Suspend(MediaSession::SuspendType::SYSTEM);
-  } else {
-    media_session_->Stop(MediaSession::SuspendType::SYSTEM);
-  }
-}
-
-void MediaSessionDelegateAndroid::OnResume(
-    JNIEnv*, const JavaParamRef<jobject>&) {
-  if (!media_session_->IsReallySuspended())
-    return;
-
-  media_session_->Resume(MediaSession::SuspendType::SYSTEM);
-}
-
-void MediaSessionDelegateAndroid::OnStartDucking(JNIEnv*, jobject) {
-  media_session_->StartDucking();
-}
-
-void MediaSessionDelegateAndroid::OnStopDucking(JNIEnv*, jobject) {
-  media_session_->StopDucking();
-}
-
-void MediaSessionDelegateAndroid::RecordSessionDuck(
-    JNIEnv*, const JavaParamRef<jobject>&) {
-  media_session_->RecordSessionDuck();
-}
-
-// static
-std::unique_ptr<MediaSessionDelegate> MediaSessionDelegate::Create(
-    MediaSession* media_session) {
-  MediaSessionDelegateAndroid* delegate =
-      new MediaSessionDelegateAndroid(media_session);
-  delegate->Initialize();
-  return std::unique_ptr<MediaSessionDelegate>(delegate);
-}
-
-}  // namespace content
diff --git a/content/browser/media/session/media_session_delegate_default.cc b/content/browser/media/session/media_session_delegate_default.cc
deleted file mode 100644
index a921b07..0000000
--- a/content/browser/media/session/media_session_delegate_default.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/media/session/media_session_delegate.h"
-
-#include "base/command_line.h"
-#include "content/browser/media/session/audio_focus_manager.h"
-#include "media/base/media_switches.h"
-
-namespace content {
-
-using AudioFocusType = AudioFocusManager::AudioFocusType;
-
-namespace {
-
-// MediaSessionDelegateDefault is the default implementation of
-// MediaSessionDelegate which only handles audio focus between WebContents.
-class MediaSessionDelegateDefault : public MediaSessionDelegate {
- public:
-  explicit MediaSessionDelegateDefault(MediaSession* media_session);
-  ~MediaSessionDelegateDefault() override;
-
-  // MediaSessionDelegate implementation.
-  bool RequestAudioFocus(
-      AudioFocusManager::AudioFocusType audio_focus_type) override;
-  void AbandonAudioFocus() override;
-
- private:
-  // Weak pointer because |this| is owned by |media_session_|.
-  MediaSession* media_session_;
-};
-
-}  // anonymous namespace
-
-MediaSessionDelegateDefault::MediaSessionDelegateDefault(
-    MediaSession* media_session)
-    : media_session_(media_session) {
-}
-
-MediaSessionDelegateDefault::~MediaSessionDelegateDefault() = default;
-
-bool MediaSessionDelegateDefault::RequestAudioFocus(
-    AudioFocusManager::AudioFocusType audio_focus_type) {
-  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableDefaultMediaSession)) {
-    return true;
-  }
-
-  AudioFocusManager::GetInstance()->RequestAudioFocus(media_session_,
-                                                      audio_focus_type);
-  return true;
-}
-
-void MediaSessionDelegateDefault::AbandonAudioFocus() {
-  AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_);
-}
-
-// static
-std::unique_ptr<MediaSessionDelegate> MediaSessionDelegate::Create(
-    MediaSession* media_session) {
-  return std::unique_ptr<MediaSessionDelegate>(
-      new MediaSessionDelegateDefault(media_session));
-}
-
-}  // namespace content
diff --git a/content/browser/media/session/media_session_observer.h b/content/browser/media/session/media_session_player_observer.h
similarity index 65%
rename from content/browser/media/session/media_session_observer.h
rename to content/browser/media/session/media_session_player_observer.h
index 4d9f4d49..95ba261 100644
--- a/content/browser/media/session/media_session_observer.h
+++ b/content/browser/media/session/media_session_player_observer.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_OBSERVER_H_
-#define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_OBSERVER_H_
+#ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_PLAYER_OBSERVER_H_
+#define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_PLAYER_OBSERVER_H_
 
 namespace content {
 
-class MediaSessionObserver {
+class MediaSessionPlayerObserver {
  public:
-  MediaSessionObserver() = default;
-  virtual ~MediaSessionObserver() = default;
+  MediaSessionPlayerObserver() = default;
+  virtual ~MediaSessionPlayerObserver() = default;
 
   // The given |player_id| has been suspended by the MediaSession.
   virtual void OnSuspend(int player_id) = 0;
@@ -26,4 +26,4 @@
 
 }  // namespace content
 
-#endif // CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_OBSERVER_H_
+#endif  // CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_PLAYER_OBSERVER_H_
diff --git a/content/browser/media/session/mock_media_session_observer.cc b/content/browser/media/session/mock_media_session_observer.cc
deleted file mode 100644
index edc213a..0000000
--- a/content/browser/media/session/mock_media_session_observer.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/media/session/mock_media_session_observer.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-
-MockMediaSessionObserver::MockMediaSessionObserver() = default;
-
-MockMediaSessionObserver::~MockMediaSessionObserver() = default;
-
-void MockMediaSessionObserver::OnSuspend(int player_id) {
-  EXPECT_GE(player_id, 0);
-  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
-
-  ++received_suspend_calls_;
-  players_[player_id].is_playing_ = false;
-}
-
-void MockMediaSessionObserver::OnResume(int player_id) {
-  EXPECT_GE(player_id, 0);
-  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
-
-  ++received_resume_calls_;
-  players_[player_id].is_playing_ = true;
-}
-
-void MockMediaSessionObserver::OnSetVolumeMultiplier(
-    int player_id, double volume_multiplier) {
-  EXPECT_GE(player_id, 0);
-  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
-
-  EXPECT_GE(volume_multiplier, 0.0f);
-  EXPECT_LE(volume_multiplier, 1.0f);
-
-  players_[player_id].volume_multiplier_ = volume_multiplier;
-}
-
-int MockMediaSessionObserver::StartNewPlayer() {
-  players_.push_back(MockPlayer(true, 1.0f));
-  return players_.size() - 1;
-}
-
-bool MockMediaSessionObserver::IsPlaying(size_t player_id) {
-  EXPECT_GT(players_.size(), player_id);
-  return players_[player_id].is_playing_;
-}
-
-double MockMediaSessionObserver::GetVolumeMultiplier(size_t player_id) {
-  EXPECT_GT(players_.size(), player_id);
-  return players_[player_id].volume_multiplier_;
-}
-
-void MockMediaSessionObserver::SetPlaying(size_t player_id, bool playing) {
-  EXPECT_GT(players_.size(), player_id);
-  players_[player_id].is_playing_ = playing;
-}
-
-int MockMediaSessionObserver::received_suspend_calls() const {
-  return received_suspend_calls_;
-}
-
-int MockMediaSessionObserver::received_resume_calls() const {
-  return received_resume_calls_;
-}
-
-}  // namespace content
diff --git a/content/browser/media/session/mock_media_session_player_observer.cc b/content/browser/media/session/mock_media_session_player_observer.cc
new file mode 100644
index 0000000..b26bc40
--- /dev/null
+++ b/content/browser/media/session/mock_media_session_player_observer.cc
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/mock_media_session_player_observer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+MockMediaSessionPlayerObserver::MockMediaSessionPlayerObserver() = default;
+
+MockMediaSessionPlayerObserver::~MockMediaSessionPlayerObserver() = default;
+
+void MockMediaSessionPlayerObserver::OnSuspend(int player_id) {
+  EXPECT_GE(player_id, 0);
+  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
+
+  ++received_suspend_calls_;
+  players_[player_id].is_playing_ = false;
+}
+
+void MockMediaSessionPlayerObserver::OnResume(int player_id) {
+  EXPECT_GE(player_id, 0);
+  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
+
+  ++received_resume_calls_;
+  players_[player_id].is_playing_ = true;
+}
+
+void MockMediaSessionPlayerObserver::OnSetVolumeMultiplier(
+    int player_id,
+    double volume_multiplier) {
+  EXPECT_GE(player_id, 0);
+  EXPECT_GT(players_.size(), static_cast<size_t>(player_id));
+
+  EXPECT_GE(volume_multiplier, 0.0f);
+  EXPECT_LE(volume_multiplier, 1.0f);
+
+  players_[player_id].volume_multiplier_ = volume_multiplier;
+}
+
+int MockMediaSessionPlayerObserver::StartNewPlayer() {
+  players_.push_back(MockPlayer(true, 1.0f));
+  return players_.size() - 1;
+}
+
+bool MockMediaSessionPlayerObserver::IsPlaying(size_t player_id) {
+  EXPECT_GT(players_.size(), player_id);
+  return players_[player_id].is_playing_;
+}
+
+double MockMediaSessionPlayerObserver::GetVolumeMultiplier(size_t player_id) {
+  EXPECT_GT(players_.size(), player_id);
+  return players_[player_id].volume_multiplier_;
+}
+
+void MockMediaSessionPlayerObserver::SetPlaying(size_t player_id,
+                                                bool playing) {
+  EXPECT_GT(players_.size(), player_id);
+  players_[player_id].is_playing_ = playing;
+}
+
+int MockMediaSessionPlayerObserver::received_suspend_calls() const {
+  return received_suspend_calls_;
+}
+
+int MockMediaSessionPlayerObserver::received_resume_calls() const {
+  return received_resume_calls_;
+}
+
+}  // namespace content
diff --git a/content/browser/media/session/mock_media_session_observer.h b/content/browser/media/session/mock_media_session_player_observer.h
similarity index 74%
rename from content/browser/media/session/mock_media_session_observer.h
rename to content/browser/media/session/mock_media_session_player_observer.h
index 03913bc..824db68 100644
--- a/content/browser/media/session/mock_media_session_observer.h
+++ b/content/browser/media/session/mock_media_session_player_observer.h
@@ -5,18 +5,18 @@
 #include <stddef.h>
 #include <vector>
 
-#include "content/browser/media/session/media_session_observer.h"
+#include "content/browser/media/session/media_session_player_observer.h"
 
 namespace content {
 
-// MockMediaSessionObserver is a mock implementation of MediaSessionObserver to
-// be used in tests.
-class MockMediaSessionObserver : public MediaSessionObserver {
+// MockMediaSessionPlayerObserver is a mock implementation of
+// MediaSessionPlayerObserver to be used in tests.
+class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
  public:
-  MockMediaSessionObserver();
-  ~MockMediaSessionObserver() override;
+  MockMediaSessionPlayerObserver();
+  ~MockMediaSessionPlayerObserver() override;
 
-  // Implements MediaSessionObserver.
+  // Implements MediaSessionPlayerObserver.
   void OnSuspend(int player_id) override;
   void OnResume(int player_id) override;
   void OnSetVolumeMultiplier(int player_id, double volume_multiplier) override;
@@ -42,8 +42,7 @@
   struct MockPlayer {
    public:
     MockPlayer(bool is_playing = true, double volume_multiplier = 1.0f)
-        : is_playing_(is_playing),
-          volume_multiplier_(volume_multiplier) {}
+        : is_playing_(is_playing), volume_multiplier_(volume_multiplier) {}
     bool is_playing_;
     double volume_multiplier_;
   };
diff --git a/content/browser/media/session/pepper_player_delegate.h b/content/browser/media/session/pepper_player_delegate.h
index bf3a39bd..db8b846 100644
--- a/content/browser/media/session/pepper_player_delegate.h
+++ b/content/browser/media/session/pepper_player_delegate.h
@@ -6,14 +6,14 @@
 #define CONTENT_BROWSER_MEDIA_SESSION_PEPPER_PLAYER_DELEGATE_H_
 
 #include "base/macros.h"
-#include "content/browser/media/session/media_session_observer.h"
+#include "content/browser/media/session/media_session_player_observer.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 
 namespace content {
 
 class PepperWebContentsObserver;
 
-class PepperPlayerDelegate : public MediaSessionObserver {
+class PepperPlayerDelegate : public MediaSessionPlayerObserver {
  public:
   // The Id can only be 0 for PepperPlayerDelegate. Declare the constant here so
   // it can be used elsewhere.
@@ -23,7 +23,7 @@
       WebContentsImpl* contents, int32_t pp_instance);
   ~PepperPlayerDelegate() override;
 
-  // MediaSessionObserver implementation.
+  // MediaSessionPlayerObserver implementation.
   void OnSuspend(int player_id) override;
   void OnResume(int player_id) override;
   void OnSetVolumeMultiplier(int player_id,
diff --git a/content/browser/notifications/notification_message_filter.cc b/content/browser/notifications/notification_message_filter.cc
index 42fd875..6efe1d2 100644
--- a/content/browser/notifications/notification_message_filter.cc
+++ b/content/browser/notifications/notification_message_filter.cc
@@ -117,7 +117,10 @@
                         OnShowPersistentNotification)
     IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_GetNotifications,
                         OnGetNotifications)
-    IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_Close, OnCloseNotification)
+    IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_Close,
+                        OnClosePlatformNotification)
+    IPC_MESSAGE_HANDLER(PlatformNotificationHostMsg_ClosePersistent,
+                        OnClosePersistentNotification)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -127,7 +130,8 @@
 void NotificationMessageFilter::OverrideThreadForMessage(
     const IPC::Message& message,
     content::BrowserThread::ID* thread) {
-  if (message.type() == PlatformNotificationHostMsg_Show::ID)
+  if (message.type() == PlatformNotificationHostMsg_Show::ID ||
+      message.type() == PlatformNotificationHostMsg_Close::ID)
     *thread = BrowserThread::UI;
 }
 
@@ -302,7 +306,26 @@
       request_id, persistent_notifications));
 }
 
-void NotificationMessageFilter::OnCloseNotification(
+void NotificationMessageFilter::OnClosePlatformNotification(
+    const GURL& origin,
+    const std::string& tag,
+    int non_persistent_notification_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (!RenderProcessHost::FromID(process_id_))
+    return;
+
+  std::string notification_id =
+      GetNotificationIdGenerator()->GenerateForNonPersistentNotification(
+          origin, tag, non_persistent_notification_id, process_id_);
+
+  if (!close_closures_.count(notification_id))
+    return;
+
+  close_closures_[notification_id].Run();
+  close_closures_.erase(notification_id);
+}
+
+void NotificationMessageFilter::OnClosePersistentNotification(
     const GURL& origin,
     const std::string& tag,
     const std::string& notification_id) {
@@ -317,38 +340,19 @@
       GetContentClient()->browser()->GetPlatformNotificationService();
   DCHECK(service);
 
-  if (NotificationIdGenerator::IsPersistentNotification(notification_id)) {
-    // There's no point in waiting until the database data has been removed
-    // before closing the notification presented to the user.
-    BrowserThread::PostTask(
-        BrowserThread::UI, FROM_HERE,
-        base::Bind(&PlatformNotificationService::ClosePersistentNotification,
-                   base::Unretained(service),  // The service is a singleton.
-                   browser_context_, notification_id));
+  // There's no point in waiting until the database data has been removed before
+  // closing the notification presented to the user. Post that task immediately.
+  BrowserThread::PostTask(
+      BrowserThread::UI, FROM_HERE,
+      base::Bind(&PlatformNotificationService::ClosePersistentNotification,
+                 base::Unretained(service),  // The service is a singleton.
+                 browser_context_, notification_id));
 
-    notification_context_->DeleteNotificationData(
-        notification_id, origin,
-        base::Bind(
-            &NotificationMessageFilter::DidDeletePersistentNotificationData,
-            weak_factory_io_.GetWeakPtr()));
-
-    return;
-  }
-
-  if (NotificationIdGenerator::IsNonPersistentNotification(notification_id)) {
-    if (!close_closures_.count(notification_id))
-      return;
-
-    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
-                            close_closures_[notification_id]);
-
-    close_closures_.erase(notification_id);
-    return;
-  }
-
-  // The renderer may have been compromised if the given |notification_id|
-  // doesn't map to either a persistent or a non-persistent notification.
-  bad_message::ReceivedBadMessage(this, bad_message::NMF_INVALID_ID_CLOSE);
+  notification_context_->DeleteNotificationData(
+      notification_id, origin,
+      base::Bind(
+          &NotificationMessageFilter::DidDeletePersistentNotificationData,
+          weak_factory_io_.GetWeakPtr()));
 }
 
 void NotificationMessageFilter::DidDeletePersistentNotificationData(
diff --git a/content/browser/notifications/notification_message_filter.h b/content/browser/notifications/notification_message_filter.h
index eaba08a..25d69c6 100644
--- a/content/browser/notifications/notification_message_filter.h
+++ b/content/browser/notifications/notification_message_filter.h
@@ -73,9 +73,12 @@
                           int64_t service_worker_registration_id,
                           const GURL& origin,
                           const std::string& filter_tag);
-  void OnCloseNotification(const GURL& origin,
-                           const std::string& tag,
-                           const std::string& notification_id);
+  void OnClosePlatformNotification(const GURL& origin,
+                                   const std::string& tag,
+                                   int non_persistent_notification_id);
+  void OnClosePersistentNotification(const GURL& origin,
+                                     const std::string& tag,
+                                     const std::string& notification_id);
 
   // Callback to be invoked by the notification context when the notification
   // data for the persistent notification may have been written, as indicated by
diff --git a/content/browser/notifications/page_notification_delegate.cc b/content/browser/notifications/page_notification_delegate.cc
index 6b3b695..4785829 100644
--- a/content/browser/notifications/page_notification_delegate.cc
+++ b/content/browser/notifications/page_notification_delegate.cc
@@ -26,8 +26,8 @@
   if (!sender)
     return;
 
-  sender->Send(new PlatformNotificationMsg_DidShow(
-      non_persistent_notification_id_, notification_id_));
+  sender->Send(
+      new PlatformNotificationMsg_DidShow(non_persistent_notification_id_));
 }
 
 void PageNotificationDelegate::NotificationClosed() {
@@ -35,8 +35,8 @@
   if (!sender)
     return;
 
-  sender->Send(new PlatformNotificationMsg_DidClose(
-      non_persistent_notification_id_, notification_id_));
+  sender->Send(
+      new PlatformNotificationMsg_DidClose(non_persistent_notification_id_));
 
   static_cast<RenderProcessHostImpl*>(sender)
       ->notification_message_filter()
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index ee6c760..39eb7da 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -464,7 +464,6 @@
   prefs.inert_visual_viewport =
       command_line.HasSwitch(switches::kInertVisualViewport);
 
-  prefs.pinch_overlay_scrollbar_thickness = 10;
   prefs.use_solid_color_scrollbars = ui::IsOverlayScrollbarEnabled();
 
   prefs.history_entry_requires_user_gesture =
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index ff4d50f..8a400e7 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -2373,14 +2373,34 @@
 
 - (void)showLookUpDictionaryOverlayFromRange:(NSRange)range
                                   targetView:(NSView*)targetView {
+  RenderWidgetHostImpl* widgetHost = renderWidgetHostView_->render_widget_host_;
+  if (!widgetHost || !widgetHost->delegate())
+    return;
+  widgetHost = widgetHost->delegate()->GetFocusedRenderWidgetHost(widgetHost);
+
+  if (!widgetHost)
+    return;
+
+  // TODO(ekaramad): The position reported by the renderer is with respect to
+  // |widgetHost|'s coordinate space with y-axis inverted to conform to AppKit
+  // coordinate system. The point will need to be transformed into root view's
+  // coordinate system (RenderWidgetHostViewMac in this case). However, since
+  // the callback is invoked on IO thread it will require some thread hopping to
+  // do so. For this reason, for now, we accept this non-ideal way of fixing the
+  // point offset manually from the view bounds. This should be revisited when
+  // fixing issues in TextInputClientMac (https://crbug.com/643233).
+  gfx::Rect root_box = renderWidgetHostView_->GetViewBounds();
+  gfx::Rect view_box = widgetHost->GetView()->GetViewBounds();
+
   TextInputClientMac::GetInstance()->GetStringFromRange(
-      renderWidgetHostView_->render_widget_host_, range,
-      ^(NSAttributedString* string, NSPoint baselinePoint) {
+      widgetHost, range, ^(NSAttributedString* string, NSPoint baselinePoint) {
+        baselinePoint.x += view_box.origin().x() - root_box.origin().x();
+        baselinePoint.y +=
+            root_box.bottom_left().y() - view_box.bottom_left().y();
         [self showLookUpDictionaryOverlayInternal:string
                                     baselinePoint:baselinePoint
                                        targetView:targetView];
-      }
-  );
+      });
 }
 
 - (void)showLookUpDictionaryOverlayAtPoint:(NSPoint)point {
diff --git a/content/child/notifications/notification_manager.cc b/content/child/notifications/notification_manager.cc
index 042cbf5b..45af387 100644
--- a/content/child/notifications/notification_manager.cc
+++ b/content/child/notifications/notification_manager.cc
@@ -43,6 +43,14 @@
 static base::LazyInstance<base::ThreadLocalPointer<NotificationManager>>::Leaky
     g_notification_manager_tls = LAZY_INSTANCE_INITIALIZER;
 
+NotificationManager::ActiveNotificationData::ActiveNotificationData(
+    blink::WebNotificationDelegate* delegate,
+    const GURL& origin,
+    const std::string& tag)
+    : delegate(delegate), origin(origin), tag(tag) {}
+
+NotificationManager::ActiveNotificationData::~ActiveNotificationData() {}
+
 NotificationManager::NotificationManager(
     ThreadSafeSender* thread_safe_sender,
     NotificationDispatcher* notification_dispatcher)
@@ -82,16 +90,18 @@
 
   GURL origin_gurl = blink::WebStringToGURL(origin.toString());
 
-  int non_persistent_notification_id =
+  int notification_id =
       notification_dispatcher_->GenerateNotificationId(CurrentWorkerId());
 
-  non_persistent_notifications_[non_persistent_notification_id] = delegate;
+  active_page_notifications_[notification_id] = ActiveNotificationData(
+      delegate, origin_gurl,
+      base::UTF16ToUTF8(base::StringPiece16(notification_data.tag)));
 
   // TODO(mkwst): This is potentially doing the wrong thing with unique
   // origins. Perhaps also 'file:', 'blob:' and 'filesystem:'. See
   // https://crbug.com/490074 for detail.
   thread_safe_sender_->Send(new PlatformNotificationHostMsg_Show(
-      non_persistent_notification_id, origin_gurl,
+      notification_id, origin_gurl,
       ToPlatformNotificationData(notification_data),
       ToNotificationResources(std::move(notification_resources))));
 }
@@ -176,61 +186,54 @@
       base::UTF16ToUTF8(base::StringPiece16(filter_tag))));
 }
 
-void NotificationManager::close(const blink::WebSecurityOrigin& origin,
-                                const blink::WebString& tag,
-                                const blink::WebString& notification_id) {
-  const std::string notification_id_str =
-      base::UTF16ToUTF8(base::StringPiece16(notification_id));
+void NotificationManager::close(blink::WebNotificationDelegate* delegate) {
+  for (auto& iter : active_page_notifications_) {
+    if (iter.second.delegate != delegate)
+      continue;
 
-  // Remove the stored local state for non-persistent notifications.
-  auto iter = non_persistent_notification_ids_.find(notification_id_str);
-  if (iter != non_persistent_notification_ids_.end()) {
-    int non_persistent_notification_id = iter->second;
-
-    non_persistent_notifications_.erase(non_persistent_notification_id);
-    non_persistent_notification_ids_.erase(iter);
+    thread_safe_sender_->Send(new PlatformNotificationHostMsg_Close(
+        iter.second.origin, iter.second.tag, iter.first));
+    active_page_notifications_.erase(iter.first);
+    return;
   }
 
-  thread_safe_sender_->Send(new PlatformNotificationHostMsg_Close(
+  // It should not be possible for Blink to call close() on a Notification which
+  // does not exist in either the pending or active notification lists.
+  NOTREACHED();
+}
+
+void NotificationManager::closePersistent(
+    const blink::WebSecurityOrigin& origin,
+    const blink::WebString& tag,
+    const blink::WebString& notification_id) {
+  thread_safe_sender_->Send(new PlatformNotificationHostMsg_ClosePersistent(
       // TODO(mkwst): This is potentially doing the wrong thing with unique
       // origins. Perhaps also 'file:', 'blob:' and 'filesystem:'. See
       // https://crbug.com/490074 for detail.
       blink::WebStringToGURL(origin.toString()),
-      base::UTF16ToUTF8(base::StringPiece16(tag)), notification_id_str));
+      base::UTF16ToUTF8(base::StringPiece16(tag)),
+      base::UTF16ToUTF8(base::StringPiece16(notification_id))));
 }
 
 void NotificationManager::notifyDelegateDestroyed(
     blink::WebNotificationDelegate* delegate) {
-  for (auto iter = non_persistent_notifications_.begin();
-       iter != non_persistent_notifications_.end(); iter++) {
-    if (iter->second != delegate)
+  for (auto& iter : active_page_notifications_) {
+    if (iter.second.delegate != delegate)
       continue;
 
-    int non_persistent_notification_id = iter->first;
-
-    // Remove the notification's ID association from the local state as well.
-    for (auto assoc_iter = non_persistent_notification_ids_.begin();
-         assoc_iter != non_persistent_notification_ids_.end(); assoc_iter++) {
-      if (assoc_iter->second != non_persistent_notification_id)
-        continue;
-
-      non_persistent_notification_ids_.erase(assoc_iter);
-      break;
-    }
-
-    non_persistent_notifications_.erase(iter);
-    break;
+    active_page_notifications_.erase(iter.first);
+    return;
   }
 }
 
 bool NotificationManager::OnMessageReceived(const IPC::Message& message) {
   bool handled = true;
   IPC_BEGIN_MESSAGE_MAP(NotificationManager, message)
-    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidShow, OnDidShow)
+    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidShow, OnDidShow);
     IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidShowPersistent,
                         OnDidShowPersistent)
-    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidClose, OnDidClose)
-    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidClick, OnDidClick)
+    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidClose, OnDidClose);
+    IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidClick, OnDidClick);
     IPC_MESSAGE_HANDLER(PlatformNotificationMsg_DidGetNotifications,
                         OnDidGetNotifications)
     IPC_MESSAGE_UNHANDLED(handled = false)
@@ -239,19 +242,12 @@
   return handled;
 }
 
-void NotificationManager::OnDidShow(int non_persistent_notification_id,
-                                    const std::string& notification_id) {
-  const auto iter =
-      non_persistent_notifications_.find(non_persistent_notification_id);
+void NotificationManager::OnDidShow(int notification_id) {
+  const auto& iter = active_page_notifications_.find(notification_id);
+  if (iter == active_page_notifications_.end())
+    return;
 
-  if (iter == non_persistent_notifications_.end())
-    return;  // The notification has been destroyed by Blink since.
-
-  non_persistent_notification_ids_[notification_id] =
-      non_persistent_notification_id;
-
-  blink::WebNotificationDelegate* delegate = iter->second;
-  delegate->didShowNotification(blink::WebString::fromUTF8(notification_id));
+  iter->second.delegate->dispatchShowEvent();
 }
 
 void NotificationManager::OnDidShowPersistent(int request_id, bool success) {
@@ -270,26 +266,22 @@
   pending_show_notification_requests_.Remove(request_id);
 }
 
-void NotificationManager::OnDidClose(int non_persistent_notification_id,
-                                     const std::string& notification_id) {
-  const auto iter =
-      non_persistent_notifications_.find(non_persistent_notification_id);
-  if (iter == non_persistent_notifications_.end())
+void NotificationManager::OnDidClose(int notification_id) {
+  const auto& iter = active_page_notifications_.find(notification_id);
+  if (iter == active_page_notifications_.end())
     return;
 
-  iter->second->didCloseNotification();
+  iter->second.delegate->dispatchCloseEvent();
 
-  non_persistent_notifications_.erase(iter);
-  non_persistent_notification_ids_.erase(notification_id);
+  active_page_notifications_.erase(iter);
 }
 
-void NotificationManager::OnDidClick(int non_persistent_notification_id) {
-  const auto iter =
-      non_persistent_notifications_.find(non_persistent_notification_id);
-  if (iter == non_persistent_notifications_.end())
+void NotificationManager::OnDidClick(int notification_id) {
+  const auto& iter = active_page_notifications_.find(notification_id);
+  if (iter == active_page_notifications_.end())
     return;
 
-  iter->second->didClickNotification();
+  iter->second.delegate->dispatchClickEvent();
 }
 
 void NotificationManager::OnDidGetNotifications(
diff --git a/content/child/notifications/notification_manager.h b/content/child/notifications/notification_manager.h
index da673a459..b556c8e1 100644
--- a/content/child/notifications/notification_manager.h
+++ b/content/child/notifications/notification_manager.h
@@ -56,9 +56,10 @@
       const blink::WebString& filter_tag,
       blink::WebServiceWorkerRegistration* service_worker_registration,
       blink::WebNotificationGetCallbacks* callbacks) override;
-  void close(const blink::WebSecurityOrigin& origin,
-             const blink::WebString& tag,
-             const blink::WebString& notification_id) override;
+  void close(blink::WebNotificationDelegate* delegate) override;
+  void closePersistent(const blink::WebSecurityOrigin& origin,
+                       const blink::WebString& tag,
+                       const blink::WebString& notification_id) override;
   void notifyDelegateDestroyed(
       blink::WebNotificationDelegate* delegate) override;
 
@@ -70,12 +71,10 @@
                       NotificationDispatcher* notification_dispatcher);
 
   // IPC message handlers.
-  void OnDidShow(int non_persistent_notification_id,
-                 const std::string& notification_id);
+  void OnDidShow(int notification_id);
   void OnDidShowPersistent(int request_id, bool success);
-  void OnDidClose(int non_persistent_notification_id,
-                  const std::string& notification_id);
-  void OnDidClick(int non_persistent_notification_id);
+  void OnDidClose(int notification_id);
+  void OnDidClick(int notification_id);
   void OnDidGetNotifications(
       int request_id,
       const std::vector<PersistentNotificationInfo>& notification_infos);
@@ -91,14 +90,21 @@
   IDMap<blink::WebNotificationShowCallbacks, IDMapOwnPointer>
       pending_show_notification_requests_;
 
-  // Map from the notification ID to the associated delegate for non-persistent
-  // notifications, for the purposes of triggering events.
-  std::unordered_map<int, blink::WebNotificationDelegate*>
-      non_persistent_notifications_;
+  // Structure holding the information for active non-persistent notifications.
+  struct ActiveNotificationData {
+    ActiveNotificationData() = default;
+    ActiveNotificationData(blink::WebNotificationDelegate* delegate,
+                           const GURL& origin,
+                           const std::string& tag);
+    ~ActiveNotificationData();
 
-  // Association from the notification ID string to the non-persistent
-  // notification ID for non-persistent notifications.
-  std::unordered_map<std::string, int> non_persistent_notification_ids_;
+    blink::WebNotificationDelegate* delegate = nullptr;
+    GURL origin;
+    std::string tag;
+  };
+
+  // Map to store the delegate associated with a notification request Id.
+  std::unordered_map<int, ActiveNotificationData> active_page_notifications_;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationManager);
 };
diff --git a/content/common/platform_notification_messages.h b/content/common/platform_notification_messages.h
index 7646c0e..02bb9f3a 100644
--- a/content/common/platform_notification_messages.h
+++ b/content/common/platform_notification_messages.h
@@ -71,18 +71,16 @@
 // Messages sent from the browser to the renderer.
 
 // Informs the renderer that the browser has displayed the notification.
-IPC_MESSAGE_CONTROL2(PlatformNotificationMsg_DidShow,
-                     int /* non_persistent_notification_id */,
-                     std::string /* notification_id */)
+IPC_MESSAGE_CONTROL1(PlatformNotificationMsg_DidShow,
+                     int /* notification_id */)
 
 // Informs the renderer that the notification has been closed.
-IPC_MESSAGE_CONTROL2(PlatformNotificationMsg_DidClose,
-                     int /* non_persistent_notification_id */,
-                     std::string /* notification_id */)
+IPC_MESSAGE_CONTROL1(PlatformNotificationMsg_DidClose,
+                     int /* notification_id */)
 
 // Informs the renderer that the notification has been clicked on.
 IPC_MESSAGE_CONTROL1(PlatformNotificationMsg_DidClick,
-                     int /* non_persistent_notification_id */)
+                     int /* notification_id */)
 
 // Reply to PlatformNotificationHostMsg_ShowPersistent indicating that a
 // persistent notification has been shown on the platform (if |success| is
@@ -124,4 +122,9 @@
 IPC_MESSAGE_CONTROL3(PlatformNotificationHostMsg_Close,
                      GURL /* origin */,
                      std::string /* tag */,
+                     int /* non_persistent_notification_id */)
+
+IPC_MESSAGE_CONTROL3(PlatformNotificationHostMsg_ClosePersistent,
+                     GURL /* origin */,
+                     std::string /* tag */,
                      std::string /* notification_id */)
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 877df73..2633cb5 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -100,6 +100,7 @@
     "java/src/org/chromium/content/browser/AppWebMessagePort.java",
     "java/src/org/chromium/content/browser/AppWebMessagePortService.java",
     "java/src/org/chromium/content/browser/ActivityContentVideoViewEmbedder.java",
+    "java/src/org/chromium/content/browser/AudioFocusDelegate.java",
     "java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java",
     "java/src/org/chromium/content/browser/BindingManager.java",
     "java/src/org/chromium/content/browser/BindingManagerImpl.java",
@@ -124,7 +125,6 @@
     "java/src/org/chromium/content/browser/InterstitialPageDelegateAndroid.java",
     "java/src/org/chromium/content/browser/JavascriptInterface.java",
     "java/src/org/chromium/content/browser/MediaResourceGetter.java",
-    "java/src/org/chromium/content/browser/MediaSessionDelegate.java",
     "java/src/org/chromium/content/browser/MediaThrottler.java",
     "java/src/org/chromium/content/browser/MemoryMonitorAndroid.java",
     "java/src/org/chromium/content/browser/MotionEventSynthesizer.java",
@@ -295,6 +295,7 @@
     "java/src/org/chromium/content/app/ChildProcessServiceImpl.java",
     "java/src/org/chromium/content/app/ContentMain.java",
     "java/src/org/chromium/content/browser/AppWebMessagePortService.java",
+    "java/src/org/chromium/content/browser/AudioFocusDelegate.java",
     "java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java",
     "java/src/org/chromium/content/browser/BrowserStartupController.java",
     "java/src/org/chromium/content/browser/ChildProcessLauncher.java",
@@ -306,7 +307,6 @@
     "java/src/org/chromium/content/browser/InterfaceRegistrarImpl.java",
     "java/src/org/chromium/content/browser/InterstitialPageDelegateAndroid.java",
     "java/src/org/chromium/content/browser/MediaResourceGetter.java",
-    "java/src/org/chromium/content/browser/MediaSessionDelegate.java",
     "java/src/org/chromium/content/browser/MediaThrottler.java",
     "java/src/org/chromium/content/browser/MemoryMonitorAndroid.java",
     "java/src/org/chromium/content/browser/MotionEventSynthesizer.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/AudioFocusDelegate.java b/content/public/android/java/src/org/chromium/content/browser/AudioFocusDelegate.java
new file mode 100644
index 0000000..b8df64a
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/AudioFocusDelegate.java
@@ -0,0 +1,115 @@
+// 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.
+
+package org.chromium.content.browser;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * AudioFocusDelegate is the Java counterpart of content::AudioFocusDelegateAndroid.
+ * It is being used to communicate from content::AudioFocusDelegateAndroid
+ * (C++) to the Android system. A AudioFocusDelegate is implementingf
+ * OnAudioFocusChangeListener, making it an audio focus holder for Android. Thus
+ * two instances of AudioFocusDelegate can't have audio focus at the same
+ * time. A AudioFocusDelegate will use the type requested from its C++
+ * counterpart and will resume its play using the same type if it were to
+ * happen, for example, when it got temporarily suspended by a transient sound
+ * like a notification.
+ */
+@JNINamespace("content")
+public class AudioFocusDelegate implements AudioManager.OnAudioFocusChangeListener {
+    private static final String TAG = "MediaSession";
+
+    private Context mContext;
+    private int mFocusType;
+    private boolean mIsDucking = false;
+
+    // Native pointer to C++ content::AudioFocusDelegateAndroid.
+    // It will be set to 0 when the native AudioFocusDelegateAndroid object is destroyed.
+    private long mNativeAudioFocusDelegateAndroid;
+
+    private AudioFocusDelegate(final Context context, long nativeAudioFocusDelegateAndroid) {
+        mContext = context;
+        mNativeAudioFocusDelegateAndroid = nativeAudioFocusDelegateAndroid;
+    }
+
+    @CalledByNative
+    private static AudioFocusDelegate create(
+            Context context, long nativeAudioFocusDelegateAndroid) {
+        return new AudioFocusDelegate(context, nativeAudioFocusDelegateAndroid);
+    }
+
+    @CalledByNative
+    private void tearDown() {
+        assert ThreadUtils.runningOnUiThread();
+        abandonAudioFocus();
+        mNativeAudioFocusDelegateAndroid = 0;
+    }
+
+    @CalledByNative
+    private boolean requestAudioFocus(boolean transientFocus) {
+        assert ThreadUtils.runningOnUiThread();
+        mFocusType = transientFocus ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+                                    : AudioManager.AUDIOFOCUS_GAIN;
+        return requestAudioFocusInternal();
+    }
+
+    @CalledByNative
+    private void abandonAudioFocus() {
+        assert ThreadUtils.runningOnUiThread();
+        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        am.abandonAudioFocus(this);
+    }
+
+    private boolean requestAudioFocusInternal() {
+        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+        int result = am.requestAudioFocus(this, AudioManager.STREAM_MUSIC, mFocusType);
+        return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+    }
+
+    @Override
+    public void onAudioFocusChange(int focusChange) {
+        assert ThreadUtils.runningOnUiThread();
+        if (mNativeAudioFocusDelegateAndroid == 0) return;
+
+        switch (focusChange) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                if (mIsDucking) {
+                    nativeOnStopDucking(mNativeAudioFocusDelegateAndroid);
+                    mIsDucking = false;
+                } else {
+                    nativeOnResume(mNativeAudioFocusDelegateAndroid);
+                }
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+                nativeOnSuspend(mNativeAudioFocusDelegateAndroid, true);
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                mIsDucking = true;
+                nativeRecordSessionDuck(mNativeAudioFocusDelegateAndroid);
+                nativeOnStartDucking(mNativeAudioFocusDelegateAndroid);
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS:
+                abandonAudioFocus();
+                nativeOnSuspend(mNativeAudioFocusDelegateAndroid, false);
+                break;
+            default:
+                Log.w(TAG, "onAudioFocusChange called with unexpected value %d", focusChange);
+                break;
+        }
+    }
+
+    private native void nativeOnSuspend(long nativeAudioFocusDelegateAndroid, boolean temporary);
+    private native void nativeOnResume(long nativeAudioFocusDelegateAndroid);
+    private native void nativeOnStartDucking(long nativeAudioFocusDelegateAndroid);
+    private native void nativeOnStopDucking(long nativeAudioFocusDelegateAndroid);
+    private native void nativeRecordSessionDuck(long nativeAudioFocusDelegateAndroid);
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/MediaSessionDelegate.java b/content/public/android/java/src/org/chromium/content/browser/MediaSessionDelegate.java
deleted file mode 100644
index 36f1c014..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/MediaSessionDelegate.java
+++ /dev/null
@@ -1,115 +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.
-
-package org.chromium.content.browser;
-
-import android.content.Context;
-import android.media.AudioManager;
-
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-
-/**
- * MediaSession is the Java counterpart of content::MediaSessionDelegateAndroid.
- * It is being used to communicate from content::MediaSessionDelegateAndroid
- * (C++) to the Android system. A MediaSessionDelegate is implementingf
- * OnAudioFocusChangeListener, making it an audio focus holder for Android. Thus
- * two instances of MediaSessionDelegate can't have audio focus at the same
- * time. A MediaSessionDelegate will use the type requested from its C++
- * counterpart and will resume its play using the same type if it were to
- * happen, for example, when it got temporarily suspended by a transient sound
- * like a notification.
- */
-@JNINamespace("content")
-public class MediaSessionDelegate implements AudioManager.OnAudioFocusChangeListener {
-    private static final String TAG = "MediaSession";
-
-    private Context mContext;
-    private int mFocusType;
-    private boolean mIsDucking = false;
-
-    // Native pointer to C++ content::MediaSessionDelegateAndroid.
-    // It will be set to 0 when the native MediaSessionDelegateAndroid object is destroyed.
-    private long mNativeMediaSessionDelegateAndroid;
-
-    private MediaSessionDelegate(final Context context, long nativeMediaSessionDelegateAndroid) {
-        mContext = context;
-        mNativeMediaSessionDelegateAndroid = nativeMediaSessionDelegateAndroid;
-    }
-
-    @CalledByNative
-    private static MediaSessionDelegate create(Context context,
-                                               long nativeMediaSessionDelegateAndroid) {
-        return new MediaSessionDelegate(context, nativeMediaSessionDelegateAndroid);
-    }
-
-    @CalledByNative
-    private void tearDown() {
-        assert ThreadUtils.runningOnUiThread();
-        abandonAudioFocus();
-        mNativeMediaSessionDelegateAndroid = 0;
-    }
-
-    @CalledByNative
-    private boolean requestAudioFocus(boolean transientFocus) {
-        assert ThreadUtils.runningOnUiThread();
-        mFocusType = transientFocus ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
-                : AudioManager.AUDIOFOCUS_GAIN;
-        return requestAudioFocusInternal();
-    }
-
-    @CalledByNative
-    private void abandonAudioFocus() {
-        assert ThreadUtils.runningOnUiThread();
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        am.abandonAudioFocus(this);
-    }
-
-    private boolean requestAudioFocusInternal() {
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-
-        int result = am.requestAudioFocus(this, AudioManager.STREAM_MUSIC, mFocusType);
-        return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
-    }
-
-    @Override
-    public void onAudioFocusChange(int focusChange) {
-        assert ThreadUtils.runningOnUiThread();
-        if (mNativeMediaSessionDelegateAndroid == 0) return;
-
-        switch (focusChange) {
-            case AudioManager.AUDIOFOCUS_GAIN:
-                if (mIsDucking) {
-                    nativeOnStopDucking(mNativeMediaSessionDelegateAndroid);
-                    mIsDucking = false;
-                } else {
-                    nativeOnResume(mNativeMediaSessionDelegateAndroid);
-                }
-                break;
-            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                nativeOnSuspend(mNativeMediaSessionDelegateAndroid, true);
-                break;
-            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                mIsDucking = true;
-                nativeRecordSessionDuck(mNativeMediaSessionDelegateAndroid);
-                nativeOnStartDucking(mNativeMediaSessionDelegateAndroid);
-                break;
-            case AudioManager.AUDIOFOCUS_LOSS:
-                abandonAudioFocus();
-                nativeOnSuspend(mNativeMediaSessionDelegateAndroid, false);
-                break;
-            default:
-                Log.w(TAG, "onAudioFocusChange called with unexpected value %d", focusChange);
-                break;
-        }
-    }
-
-    private native void nativeOnSuspend(long nativeMediaSessionDelegateAndroid, boolean temporary);
-    private native void nativeOnResume(long nativeMediaSessionDelegateAndroid);
-    private native void nativeOnStartDucking(long nativeMediaSessionDelegateAndroid);
-    private native void nativeOnStopDucking(long nativeMediaSessionDelegateAndroid);
-    private native void nativeRecordSessionDuck(long nativeMediaSessionDelegateAndroid);
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/OWNERS b/content/public/android/java/src/org/chromium/content/browser/OWNERS
index 1dbbbe30..fb4de38 100644
--- a/content/public/android/java/src/org/chromium/content/browser/OWNERS
+++ b/content/public/android/java/src/org/chromium/content/browser/OWNERS
@@ -10,8 +10,8 @@
 per-file ScreenOrientation*.java=mlamouri@chromium.org
 
 # Media Session API related
-per-file MediaSession*.java=avayvod@chromium.org
-per-file MediaSession*.java=mlamouri@chromium.org
+per-file AudioFocus*.java=avayvod@chromium.org
+per-file AudioFocus*.java=mlamouri@chromium.org
 
 # Process management related
 per-file BindingManager*.java=jaekyun@chromium.org
diff --git a/content/public/common/common_param_traits_macros.h b/content/public/common/common_param_traits_macros.h
index 12193432..0f4b8f6 100644
--- a/content/public/common/common_param_traits_macros.h
+++ b/content/public/common/common_param_traits_macros.h
@@ -152,7 +152,6 @@
   IPC_STRUCT_TRAITS_MEMBER(pepper_3d_enabled)
   IPC_STRUCT_TRAITS_MEMBER(inert_visual_viewport)
   IPC_STRUCT_TRAITS_MEMBER(record_whole_document)
-  IPC_STRUCT_TRAITS_MEMBER(pinch_overlay_scrollbar_thickness)
   IPC_STRUCT_TRAITS_MEMBER(use_solid_color_scrollbars)
   IPC_STRUCT_TRAITS_MEMBER(flash_3d_enabled)
   IPC_STRUCT_TRAITS_MEMBER(flash_stage3d_enabled)
diff --git a/content/public/common/web_preferences.cc b/content/public/common/web_preferences.cc
index c485914..29426a13 100644
--- a/content/public/common/web_preferences.cc
+++ b/content/public/common/web_preferences.cc
@@ -171,7 +171,6 @@
       smart_insert_delete_enabled(false),
 #endif
       spatial_navigation_enabled(false),
-      pinch_overlay_scrollbar_thickness(0),
       use_solid_color_scrollbars(false),
       navigate_on_drag_drop(true),
       v8_cache_options(V8_CACHE_OPTIONS_DEFAULT),
diff --git a/content/public/common/web_preferences.h b/content/public/common/web_preferences.h
index 097acbc..bdceefc 100644
--- a/content/public/common/web_preferences.h
+++ b/content/public/common/web_preferences.h
@@ -184,7 +184,6 @@
   bool initialize_at_minimum_page_scale;
   bool smart_insert_delete_enabled;
   bool spatial_navigation_enabled;
-  int pinch_overlay_scrollbar_thickness;
   bool use_solid_color_scrollbars;
   bool navigate_on_drag_drop;
   V8CacheOptions v8_cache_options;
diff --git a/content/public/test/text_input_test_utils.h b/content/public/test/text_input_test_utils.h
index 7785b67..080d0fa 100644
--- a/content/public/test/text_input_test_utils.h
+++ b/content/public/test/text_input_test_utils.h
@@ -13,6 +13,14 @@
 #include "ui/base/ime/text_input_mode.h"
 #include "ui/base/ime/text_input_type.h"
 
+#ifdef OS_MACOSX
+#include "content/public/browser/browser_message_filter.h"
+#endif
+
+namespace ipc {
+class Message;
+}
+
 namespace gfx {
 class Range;
 }
@@ -23,6 +31,8 @@
 
 namespace content {
 
+class MessageLoopRunner;
+class RenderProcessHost;
 class RenderWidgetHost;
 class RenderWidgetHostView;
 class RenderWidgetHostViewBase;
@@ -180,6 +190,48 @@
   TestInputMethodObserver();
 };
 
+#ifdef OS_MACOSX
+// The test message filter for TextInputClientMac incoming messages from the
+// renderer.
+// NOTE: This filter should be added to the intended RenderProcessHost before
+// the actual TextInputClientMessageFilter, otherwise, the messages
+// will be handled and will never receive this filter.
+class TestTextInputClientMessageFilter : public BrowserMessageFilter {
+ public:
+  // Creates the filter and adds itself to |host|.
+  TestTextInputClientMessageFilter(RenderProcessHost* host);
+
+  // Wait until the IPC TextInputClientReplyMsg_GotStringForRange arrives.
+  void WaitForStringFromRange();
+
+  // BrowserMessageFilter overrides.
+  bool OnMessageReceived(const IPC::Message& message) override;
+
+  RenderProcessHost* process() const { return host_; }
+  std::string string_from_range() { return string_from_range_; }
+
+ private:
+  ~TestTextInputClientMessageFilter() override;
+  RenderProcessHost* const host_;
+  std::string string_from_range_;
+  bool received_string_from_range_;
+  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestTextInputClientMessageFilter);
+};
+
+// Requests the |tab_view| for the definition of the word identified by the
+// given selection range. |range| identifies a word in the focused
+// RenderWidgetHost underneath |tab_view| which may be different than the
+// RenderWidgetHost corresponding to |tab_view|.
+void AskForLookUpDictionaryForRange(RenderWidgetHostView* tab_view,
+                                    const gfx::Range& range);
+
+// Returns the total count of NSWindows instances which belong to the currently
+// running NSApplication.
+size_t GetOpenNSWindowsCount();
+#endif
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_TEST_TEXT_INPUT_TEST_UTILS_H_
diff --git a/content/public/test/text_input_test_utils_mac.mm b/content/public/test/text_input_test_utils_mac.mm
new file mode 100644
index 0000000..29de863
--- /dev/null
+++ b/content/public/test/text_input_test_utils_mac.mm
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/test/text_input_test_utils.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/browser/site_instance_impl.h"
+#include "content/common/mac/attributed_string_coder.h"
+#include "content/common/text_input_client_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/test/test_utils.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace content {
+
+TestTextInputClientMessageFilter::TestTextInputClientMessageFilter(
+    RenderProcessHost* host)
+    : BrowserMessageFilter(TextInputClientMsgStart),
+      host_(host),
+      received_string_from_range_(false) {
+  host->AddFilter(this);
+}
+
+TestTextInputClientMessageFilter::~TestTextInputClientMessageFilter() {}
+
+void TestTextInputClientMessageFilter::WaitForStringFromRange() {
+  if (received_string_from_range_)
+    return;
+  message_loop_runner_ = new MessageLoopRunner();
+  message_loop_runner_->Run();
+}
+
+bool TestTextInputClientMessageFilter::OnMessageReceived(
+    const IPC::Message& message) {
+  if (message.type() == TextInputClientReplyMsg_GotStringForRange::ID) {
+    received_string_from_range_ = true;
+
+    // Now decode the string to get the word.
+    TextInputClientReplyMsg_GotStringForRange::Param params;
+    TextInputClientReplyMsg_GotStringForRange::Read(&message, &params);
+    const mac::AttributedStringCoder::EncodedString& encoded_string =
+        std::get<0>(params);
+    NSAttributedString* decoded =
+        mac::AttributedStringCoder::Decode(&encoded_string);
+    string_from_range_ = base::SysNSStringToUTF8([decoded string]);
+
+    // Stop the message loop if it is running.
+    if (message_loop_runner_) {
+      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+                              message_loop_runner_->QuitClosure());
+    }
+  }
+
+  // unhandled - leave it for the actual TextInputClientMessageFilter to handle.
+  return false;
+}
+
+void AskForLookUpDictionaryForRange(RenderWidgetHostView* tab_view,
+                                    const gfx::Range& range) {
+  RenderWidgetHostViewCocoa* cocoa_view =
+      static_cast<RenderWidgetHostViewMac*>(tab_view)->cocoa_view();
+
+  // Explicitly ask for the dictionary for the given range.
+  [cocoa_view showLookUpDictionaryOverlayFromRange:range.ToNSRange()
+                                        targetView:cocoa_view];
+}
+
+size_t GetOpenNSWindowsCount() {
+  return [[NSApp windows] count];
+}
+
+}  // namespace content
diff --git a/content/renderer/manifest/manifest_parser.h b/content/renderer/manifest/manifest_parser.h
index bf5acad6..9355930 100644
--- a/content/renderer/manifest/manifest_parser.h
+++ b/content/renderer/manifest/manifest_parser.h
@@ -80,59 +80,59 @@
                 const GURL& base_url);
 
   // Parses the 'name' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member
   // Returns the parsed string if any, a null string if the parsing failed.
   base::NullableString16 ParseName(const base::DictionaryValue& dictionary);
 
   // Parses the 'short_name' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-short-name-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-short-name-member
   // Returns the parsed string if any, a null string if the parsing failed.
   base::NullableString16 ParseShortName(
       const base::DictionaryValue& dictionary);
 
   // Parses the 'scope' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-scope-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-scope-member
   // Returns the parsed GURL if any, an empty GURL if the parsing failed.
   GURL ParseScope(const base::DictionaryValue& dictionary,
                   const GURL& start_url);
 
   // Parses the 'start_url' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-start_url-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-start_url-member
   // Returns the parsed GURL if any, an empty GURL if the parsing failed.
   GURL ParseStartURL(const base::DictionaryValue& dictionary);
 
   // Parses the 'display' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-display-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-display-member
   // Returns the parsed DisplayMode if any, WebDisplayModeUndefined if the
   // parsing failed.
   blink::WebDisplayMode ParseDisplay(const base::DictionaryValue& dictionary);
 
   // Parses the 'orientation' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member
   // Returns the parsed WebScreenOrientationLockType if any,
   // WebScreenOrientationLockDefault if the parsing failed.
   blink::WebScreenOrientationLockType ParseOrientation(
       const base::DictionaryValue& dictionary);
 
   // Parses the 'src' field of an icon, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-src-member-of-an-icon
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-src-member-of-an-image
   // Returns the parsed GURL if any, an empty GURL if the parsing failed.
   GURL ParseIconSrc(const base::DictionaryValue& icon);
 
   // Parses the 'type' field of an icon, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-type-member-of-an-icon
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-type-member-of-an-image
   // Returns the parsed string if any, an empty string if the parsing failed.
   base::string16 ParseIconType(const base::DictionaryValue& icon);
 
   // Parses the 'sizes' field of an icon, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-a-sizes-member-of-an-icon
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-a-sizes-member-of-an-image
   // Returns a vector of gfx::Size with the successfully parsed sizes, if any.
   // An empty vector if the field was not present or empty. "Any" is represented
   // by gfx::Size(0, 0).
   std::vector<gfx::Size> ParseIconSizes(const base::DictionaryValue& icon);
 
   // Parses the 'icons' field of a Manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-icons-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-an-array-of-images
   // Returns a vector of Manifest::Icon with the successfully parsed icons, if
   // any. An empty vector if the field was not present or empty.
   std::vector<Manifest::Icon> ParseIcons(
@@ -170,13 +170,13 @@
   bool ParsePreferRelatedApplications(const base::DictionaryValue& dictionary);
 
   // Parses the 'theme_color' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-theme_color-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-theme_color-member
   // Returns the parsed theme color if any,
   // Manifest::kInvalidOrMissingColor if the parsing failed.
   int64_t ParseThemeColor(const base::DictionaryValue& dictionary);
 
   // Parses the 'background_color' field of the manifest, as defined in:
-  // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-background_color-member
+  // https://w3c.github.io/manifest/#dfn-steps-for-processing-the-background_color-member
   // Returns the parsed background color if any,
   // Manifest::kInvalidOrMissingColor if the parsing failed.
   int64_t ParseBackgroundColor(const base::DictionaryValue& dictionary);
diff --git a/content/renderer/media/gpu/rtc_video_encoder.cc b/content/renderer/media/gpu/rtc_video_encoder.cc
index 34b920d9..9e7a12e 100644
--- a/content/renderer/media/gpu/rtc_video_encoder.cc
+++ b/content/renderer/media/gpu/rtc_video_encoder.cc
@@ -752,7 +752,10 @@
            << ", width=" << codec_settings->width
            << ", height=" << codec_settings->height
            << ", startBitrate=" << codec_settings->startBitrate;
-  DCHECK(!impl_.get());
+  if (impl_) {
+    DVLOG(1) << "Release because of reinitialization";
+    Release();
+  }
 
   impl_ = new Impl(gpu_factories_, video_codec_type_);
   const media::VideoCodecProfile profile = WebRTCVideoCodecToVideoCodecProfile(
diff --git a/content/renderer/media/gpu/rtc_video_encoder_unittest.cc b/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
index 475d4c92..3cdab3f7 100644
--- a/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
+++ b/content/renderer/media/gpu/rtc_video_encoder_unittest.cc
@@ -46,19 +46,28 @@
         idle_waiter_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                      base::WaitableEvent::InitialState::NOT_SIGNALED) {}
 
+  media::MockVideoEncodeAccelerator* ExpectCreateInitAndDestroyVEA() {
+    // The VEA will be owned by the RTCVideoEncoder once
+    // factory.CreateVideoEncodeAccelerator() is called.
+    media::MockVideoEncodeAccelerator* mock_vea =
+        new media::MockVideoEncodeAccelerator();
+
+    EXPECT_CALL(*mock_gpu_factories_.get(), DoCreateVideoEncodeAccelerator())
+        .WillRepeatedly(Return(mock_vea));
+    EXPECT_CALL(*mock_vea, Initialize(_, _, _, _, _))
+        .WillOnce(Invoke(this, &RTCVideoEncoderTest::Initialize));
+    EXPECT_CALL(*mock_vea, UseOutputBitstreamBuffer(_)).Times(3);
+    EXPECT_CALL(*mock_vea, Destroy()).Times(1);
+    return mock_vea;
+  }
+
   void SetUp() override {
     DVLOG(3) << __FUNCTION__;
     ASSERT_TRUE(encoder_thread_.Start());
-    mock_vea_ = new media::MockVideoEncodeAccelerator();
 
     EXPECT_CALL(*mock_gpu_factories_.get(), GetTaskRunner())
         .WillRepeatedly(Return(encoder_thread_.task_runner()));
-    EXPECT_CALL(*mock_gpu_factories_.get(), DoCreateVideoEncodeAccelerator())
-        .WillRepeatedly(Return(mock_vea_));
-    EXPECT_CALL(*mock_vea_, Initialize(_, _, _, _, _))
-        .WillOnce(Invoke(this, &RTCVideoEncoderTest::Initialize));
-    EXPECT_CALL(*mock_vea_, UseOutputBitstreamBuffer(_)).Times(3);
-    EXPECT_CALL(*mock_vea_, Destroy()).Times(1);
+    mock_vea_ = ExpectCreateInitAndDestroyVEA();
   }
 
   void TearDown() override {
@@ -145,6 +154,17 @@
   EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
 }
 
+TEST_P(RTCVideoEncoderTest, RepeatedInitSucceeds) {
+  const webrtc::VideoCodecType codec_type = GetParam();
+  CreateEncoder(codec_type);
+  webrtc::VideoCodec codec = GetDefaultCodec();
+  codec.codecType = codec_type;
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
+
+  ExpectCreateInitAndDestroyVEA();
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
+}
+
 TEST_F(RTCVideoEncoderTest, EncodeScaledFrame) {
   const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP8;
   CreateEncoder(codec_type);
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 7dd11dd..0891c86 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1166,8 +1166,6 @@
   settings->setMainFrameResizesAreOrientationChanges(
       prefs.main_frame_resizes_are_orientation_changes);
 
-  settings->setPinchOverlayScrollbarThickness(
-      prefs.pinch_overlay_scrollbar_thickness);
   settings->setUseSolidColorScrollbars(prefs.use_solid_color_scrollbars);
 
   settings->setShowContextMenuOnMouseUp(prefs.context_menu_on_mouse_up);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f6f8b8f..764d2af 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -111,6 +111,7 @@
     "../public/test/test_web_ui.h",
     "../public/test/text_input_test_utils.cc",
     "../public/test/text_input_test_utils.h",
+    "../public/test/text_input_test_utils_mac.mm",
     "../public/test/unittest_test_suite.cc",
     "../public/test/unittest_test_suite.h",
     "../public/test/web_contents_tester.cc",
@@ -580,11 +581,11 @@
     "../browser/media/media_redirect_browsertest.cc",
     "../browser/media/media_source_browsertest.cc",
     "../browser/media/midi_browsertest.cc",
+    "../browser/media/session/audio_focus_delegate_default_browsertest.cc",
     "../browser/media/session/media_session_browsertest.cc",
-    "../browser/media/session/media_session_delegate_default_browsertest.cc",
     "../browser/media/session/media_session_visibility_browsertest.cc",
-    "../browser/media/session/mock_media_session_observer.cc",
-    "../browser/media/session/mock_media_session_observer.h",
+    "../browser/media/session/mock_media_session_player_observer.cc",
+    "../browser/media/session/mock_media_session_player_observer.h",
     "../browser/memory/memory_coordinator_browsertest.cc",
     "../browser/memory/memory_pressure_controller_impl_browsertest.cc",
     "../browser/message_port_provider_browsertest.cc",
@@ -785,12 +786,12 @@
   if (is_android) {
     sources += [
       "../browser/accessibility/android_granularity_movement_browsertest.cc",
-      "../browser/media/session/media_session_delegate_android_browsertest.cc",
+      "../browser/media/session/audio_focus_delegate_android_browsertest.cc",
       "../shell/android/browsertests_apk/content_browser_tests_jni_onload.cc",
     ]
     sources -= [
       "../browser/battery_status/battery_monitor_impl_browsertest.cc",
-      "../browser/media/session/media_session_delegate_default_browsertest.cc",
+      "../browser/media/session/audio_focus_delegate_default_browsertest.cc",
     ]
     deps += [
       ":content_browsertests_java",
diff --git a/device/bluetooth/bluetooth_adapter.cc b/device/bluetooth/bluetooth_adapter.cc
index 1701b80..86b3769 100644
--- a/device/bluetooth/bluetooth_adapter.cc
+++ b/device/bluetooth/bluetooth_adapter.cc
@@ -166,23 +166,23 @@
 }
 
 void BluetoothAdapter::NotifyAdapterPoweredChanged(bool powered) {
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    AdapterPoweredChanged(this, powered));
+  for (auto& observer : observers_)
+    observer.AdapterPoweredChanged(this, powered);
 }
 
 void BluetoothAdapter::NotifyDeviceChanged(BluetoothDevice* device) {
   DCHECK(device);
   DCHECK_EQ(device->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    DeviceChanged(this, device));
+  for (auto& observer : observers_)
+    observer.DeviceChanged(this, device);
 }
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
 void BluetoothAdapter::NotifyDevicePairedChanged(BluetoothDevice* device,
                                                  bool new_paired_status) {
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    DevicePairedChanged(this, device, new_paired_status));
+  for (auto& observer : observers_)
+    observer.DevicePairedChanged(this, device, new_paired_status);
 }
 #endif
 
@@ -190,55 +190,55 @@
     BluetoothRemoteGattService* service) {
   DCHECK_EQ(service->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattServiceAdded(this, service->GetDevice(), service));
+  for (auto& observer : observers_)
+    observer.GattServiceAdded(this, service->GetDevice(), service);
 }
 
 void BluetoothAdapter::NotifyGattServiceRemoved(
     BluetoothRemoteGattService* service) {
   DCHECK_EQ(service->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattServiceRemoved(this, service->GetDevice(), service));
+  for (auto& observer : observers_)
+    observer.GattServiceRemoved(this, service->GetDevice(), service);
 }
 
 void BluetoothAdapter::NotifyGattServiceChanged(
     BluetoothRemoteGattService* service) {
   DCHECK_EQ(service->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattServiceChanged(this, service));
+  for (auto& observer : observers_)
+    observer.GattServiceChanged(this, service);
 }
 
 void BluetoothAdapter::NotifyGattServicesDiscovered(BluetoothDevice* device) {
   DCHECK(device->GetAdapter() == this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattServicesDiscovered(this, device));
+  for (auto& observer : observers_)
+    observer.GattServicesDiscovered(this, device);
 }
 
 void BluetoothAdapter::NotifyGattDiscoveryComplete(
     BluetoothRemoteGattService* service) {
   DCHECK_EQ(service->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattDiscoveryCompleteForService(this, service));
+  for (auto& observer : observers_)
+    observer.GattDiscoveryCompleteForService(this, service);
 }
 
 void BluetoothAdapter::NotifyGattCharacteristicAdded(
     BluetoothRemoteGattCharacteristic* characteristic) {
   DCHECK_EQ(characteristic->GetService()->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattCharacteristicAdded(this, characteristic));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicAdded(this, characteristic);
 }
 
 void BluetoothAdapter::NotifyGattCharacteristicRemoved(
     BluetoothRemoteGattCharacteristic* characteristic) {
   DCHECK_EQ(characteristic->GetService()->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattCharacteristicRemoved(this, characteristic));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicRemoved(this, characteristic);
 }
 
 void BluetoothAdapter::NotifyGattDescriptorAdded(
@@ -247,8 +247,8 @@
       descriptor->GetCharacteristic()->GetService()->GetDevice()->GetAdapter(),
       this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattDescriptorAdded(this, descriptor));
+  for (auto& observer : observers_)
+    observer.GattDescriptorAdded(this, descriptor);
 }
 
 void BluetoothAdapter::NotifyGattDescriptorRemoved(
@@ -257,8 +257,8 @@
       descriptor->GetCharacteristic()->GetService()->GetDevice()->GetAdapter(),
       this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattDescriptorRemoved(this, descriptor));
+  for (auto& observer : observers_)
+    observer.GattDescriptorRemoved(this, descriptor);
 }
 
 void BluetoothAdapter::NotifyGattCharacteristicValueChanged(
@@ -266,9 +266,8 @@
     const std::vector<uint8_t>& value) {
   DCHECK_EQ(characteristic->GetService()->GetDevice()->GetAdapter(), this);
 
-  FOR_EACH_OBSERVER(
-      BluetoothAdapter::Observer, observers_,
-      GattCharacteristicValueChanged(this, characteristic, value));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicValueChanged(this, characteristic, value);
 }
 
 void BluetoothAdapter::NotifyGattDescriptorValueChanged(
@@ -278,8 +277,8 @@
       descriptor->GetCharacteristic()->GetService()->GetDevice()->GetAdapter(),
       this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    GattDescriptorValueChanged(this, descriptor, value));
+  for (auto& observer : observers_)
+    observer.GattDescriptorValueChanged(this, descriptor, value);
 }
 
 BluetoothAdapter::BluetoothAdapter() : weak_ptr_factory_(this) {
@@ -399,8 +398,8 @@
         devices_.take_and_erase(it);
     it = next;
 
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceRemoved(this, removed_device.get()));
+    for (auto& observer : observers_)
+      observer.DeviceRemoved(this, removed_device.get());
   }
 }
 
diff --git a/device/bluetooth/bluetooth_adapter_android.cc b/device/bluetooth/bluetooth_adapter_android.cc
index b4ab2ac..41228bb 100644
--- a/device/bluetooth/bluetooth_adapter_android.cc
+++ b/device/bluetooth/bluetooth_adapter_android.cc
@@ -220,11 +220,11 @@
 
   if (is_new_device) {
     devices_.add(device_address, std::move(device_android_owner));
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceAdded(this, device_android));
+    for (auto& observer : observers_)
+      observer.DeviceAdded(this, device_android);
   } else {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceChanged(this, device_android));
+    for (auto& observer : observers_)
+      observer.DeviceChanged(this, device_android);
   }
 }
 
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm
index e383f9d..16a876e 100644
--- a/device/bluetooth/bluetooth_adapter_mac.mm
+++ b/device/bluetooth/bluetooth_adapter_mac.mm
@@ -234,9 +234,8 @@
     num_discovery_sessions_ = 0;
     MarkDiscoverySessionsAsInactive();
   }
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
-                    observers_,
-                    AdapterDiscoveringChanged(this, false));
+  for (auto& observer : observers_)
+    observer.AdapterDiscoveringChanged(this, false);
 }
 
 void BluetoothAdapterMac::DeviceConnected(IOBluetoothDevice* device) {
@@ -298,9 +297,8 @@
 
   DVLOG(1) << "Added a discovery session";
   num_discovery_sessions_++;
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
-                    observers_,
-                    AdapterDiscoveringChanged(this, true));
+  for (auto& observer : observers_)
+    observer.AdapterDiscoveringChanged(this, true);
   callback.Run();
 }
 
@@ -431,8 +429,8 @@
       FROM_HERE_WITH_EXPLICIT_FUNCTION(
           "461181 BluetoothAdapterMac::PollAdapter::AdapterPresentChanged"));
   if (was_present != is_present) {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterPresentChanged(this, is_present));
+    for (auto& observer : observers_)
+      observer.AdapterPresentChanged(this, is_present);
   }
 
   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
@@ -442,8 +440,8 @@
           "461181 BluetoothAdapterMac::PollAdapter::AdapterPowerChanged"));
   if (classic_powered_ != classic_powered) {
     classic_powered_ = classic_powered;
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterPoweredChanged(this, classic_powered_));
+    for (auto& observer : observers_)
+      observer.AdapterPoweredChanged(this, classic_powered_);
   }
 
   // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/461181
@@ -484,8 +482,8 @@
   devices_.set(device_address, base::WrapUnique(device_classic));
   VLOG(1) << "Adding new classic device: " << device_classic->GetAddress();
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    DeviceAdded(this, device_classic));
+  for (auto& observer : observers_)
+    observer.DeviceAdded(this, device_classic);
 }
 
 void BluetoothAdapterMac::LowEnergyDeviceUpdated(
@@ -562,11 +560,11 @@
     std::string device_address =
         BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
     devices_.add(device_address, std::unique_ptr<BluetoothDevice>(device_mac));
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceAdded(this, device_mac));
+    for (auto& observer : observers_)
+      observer.DeviceAdded(this, device_mac);
   } else {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceChanged(this, device_mac));
+    for (auto& observer : observers_)
+      observer.DeviceChanged(this, device_mac);
   }
 }
 
diff --git a/device/bluetooth/bluetooth_adapter_win.cc b/device/bluetooth/bluetooth_adapter_win.cc
index e440fec..1bacaac 100644
--- a/device/bluetooth/bluetooth_adapter_win.cc
+++ b/device/bluetooth/bluetooth_adapter_win.cc
@@ -128,8 +128,8 @@
   on_start_discovery_callbacks_.clear();
 
   if (success) {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterDiscoveringChanged(this, true));
+    for (auto& observer : observers_)
+      observer.AdapterDiscoveringChanged(this, true);
 
     // If there are stop discovery requests, post the stop discovery again.
     MaybePostStopDiscoveryTask();
@@ -153,8 +153,8 @@
   num_discovery_listeners_ = 0;
   on_stop_discovery_callbacks_.clear();
   if (was_discovering)
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterDiscoveringChanged(this, false));
+    for (auto& observer : observers_)
+      observer.AdapterDiscoveringChanged(this, false);
 
   // If there are start discovery requests, post the start discovery again.
   MaybePostStartDiscoveryTask();
@@ -212,13 +212,13 @@
   bool is_present = !state.address.empty();
   address_ = BluetoothDevice::CanonicalizeAddress(state.address);
   if (was_present != is_present) {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterPresentChanged(this, is_present));
+    for (auto& observer : observers_)
+      observer.AdapterPresentChanged(this, is_present);
   }
   if (powered_ != state.powered) {
     powered_ = state.powered;
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      AdapterPoweredChanged(this, powered_));
+    for (auto& observer : observers_)
+      observer.AdapterPoweredChanged(this, powered_);
   }
   if (!initialized_) {
     initialized_ = true;
@@ -258,8 +258,8 @@
        ++iter) {
     std::unique_ptr<BluetoothDevice> device_win =
         devices_.take_and_erase(*iter);
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceRemoved(this, device_win.get()));
+    for (auto& observer : observers_)
+      observer.DeviceRemoved(this, device_win.get());
   }
 
   // Process added and (maybe) changed devices in one pass
@@ -278,9 +278,8 @@
                                  socket_thread_, NULL, net::NetLogSource());
       devices_.set(device_state->address,
                    std::unique_ptr<BluetoothDevice>(device_win));
-      FOR_EACH_OBSERVER(BluetoothAdapter::Observer,
-                        observers_,
-                        DeviceAdded(this, device_win));
+      for (auto& observer : observers_)
+        observer.DeviceAdded(this, device_win);
     } else if (changed_devices.find(device_state->address) !=
                changed_devices.end()) {
       DevicesMap::const_iterator iter = devices_.find(device_state->address);
@@ -289,8 +288,8 @@
           static_cast<BluetoothDeviceWin*>(iter->second);
       if (!device_win->IsEqual(*device_state)) {
         device_win->Update(*device_state);
-        FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                          DeviceChanged(this, device_win));
+        for (auto& observer : observers_)
+          observer.DeviceChanged(this, device_win);
       }
       // Above IsEqual returns true if device name, address, status and services
       // (primary services of BLE device) are the same. However, in BLE tests,
diff --git a/device/bluetooth/bluetooth_task_manager_win.cc b/device/bluetooth/bluetooth_task_manager_win.cc
index df0b0fc..4573fb9 100644
--- a/device/bluetooth/bluetooth_task_manager_win.cc
+++ b/device/bluetooth/bluetooth_task_manager_win.cc
@@ -361,27 +361,27 @@
 
 void BluetoothTaskManagerWin::OnAdapterStateChanged(const AdapterState* state) {
   DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(BluetoothTaskManagerWin::Observer, observers_,
-                    AdapterStateChanged(*state));
+  for (auto& observer : observers_)
+    observer.AdapterStateChanged(*state);
 }
 
 void BluetoothTaskManagerWin::OnDiscoveryStarted(bool success) {
   DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(BluetoothTaskManagerWin::Observer, observers_,
-                    DiscoveryStarted(success));
+  for (auto& observer : observers_)
+    observer.DiscoveryStarted(success);
 }
 
 void BluetoothTaskManagerWin::OnDiscoveryStopped() {
   DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(BluetoothTaskManagerWin::Observer, observers_,
-                    DiscoveryStopped());
+  for (auto& observer : observers_)
+    observer.DiscoveryStopped();
 }
 
 void BluetoothTaskManagerWin::OnDevicesPolled(
     const ScopedVector<DeviceState>* devices) {
   DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
-  FOR_EACH_OBSERVER(
-      BluetoothTaskManagerWin::Observer, observers_, DevicesPolled(*devices));
+  for (auto& observer : observers_)
+    observer.DevicesPolled(*devices);
 }
 
 void BluetoothTaskManagerWin::PollAdapter() {
diff --git a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
index ad4510a..6377518b 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
@@ -539,8 +539,8 @@
   devices_.set(device_bluez->GetAddress(),
                std::unique_ptr<BluetoothDevice>(device_bluez));
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    DeviceAdded(this, device_bluez));
+  for (auto& observer : observers_)
+    observer.DeviceAdded(this, device_bluez);
 }
 
 void BluetoothAdapterBlueZ::DeviceRemoved(const dbus::ObjectPath& object_path) {
@@ -552,8 +552,8 @@
       std::unique_ptr<BluetoothDevice> scoped_device =
           devices_.take_and_erase(iter->first);
 
-      FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                        DeviceRemoved(this, device_bluez));
+      for (auto& observer : observers_)
+        observer.DeviceRemoved(this, device_bluez);
       return;
     }
   }
@@ -1004,16 +1004,16 @@
   devices_swapped.swap(devices_);
 
   for (auto& iter : devices_swapped) {
-    FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                      DeviceRemoved(this, iter.second));
+    for (auto& observer : observers_)
+      observer.DeviceRemoved(this, iter.second);
   }
 
   PresentChanged(false);
 }
 
 void BluetoothAdapterBlueZ::DiscoverableChanged(bool discoverable) {
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    AdapterDiscoverableChanged(this, discoverable));
+  for (auto& observer : observers_)
+    observer.AdapterDiscoverableChanged(this, discoverable);
 }
 
 void BluetoothAdapterBlueZ::DiscoveringChanged(bool discovering) {
@@ -1026,13 +1026,13 @@
     num_discovery_sessions_ = 0;
     MarkDiscoverySessionsAsInactive();
   }
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    AdapterDiscoveringChanged(this, discovering));
+  for (auto& observer : observers_)
+    observer.AdapterDiscoveringChanged(this, discovering);
 }
 
 void BluetoothAdapterBlueZ::PresentChanged(bool present) {
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    AdapterPresentChanged(this, present));
+  for (auto& observer : observers_)
+    observer.AdapterPresentChanged(this, present);
 }
 
 void BluetoothAdapterBlueZ::NotifyDeviceAddressChanged(
@@ -1040,8 +1040,8 @@
     const std::string& old_address) {
   DCHECK(device->adapter_ == this);
 
-  FOR_EACH_OBSERVER(BluetoothAdapter::Observer, observers_,
-                    DeviceAddressChanged(this, device, old_address));
+  for (auto& observer : observers_)
+    observer.DeviceAddressChanged(this, device, old_address);
 }
 
 void BluetoothAdapterBlueZ::UseProfile(
diff --git a/device/bluetooth/bluez/bluetooth_advertisement_bluez.cc b/device/bluetooth/bluez/bluetooth_advertisement_bluez.cc
index d03fd95..0adece73 100644
--- a/device/bluetooth/bluez/bluetooth_advertisement_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_advertisement_bluez.cc
@@ -130,8 +130,8 @@
 void BluetoothAdvertisementBlueZ::Released() {
   LOG(WARNING) << "Advertisement released.";
   provider_.reset();
-  FOR_EACH_OBSERVER(BluetoothAdvertisement::Observer, observers_,
-                    AdvertisementReleased(this));
+  for (auto& observer : observers_)
+    observer.AdvertisementReleased(this);
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/bluetooth_adapter_client.cc b/device/bluetooth/dbus/bluetooth_adapter_client.cc
index d763696d..e948e6aa 100644
--- a/device/bluetooth/dbus/bluetooth_adapter_client.cc
+++ b/device/bluetooth/dbus/bluetooth_adapter_client.cc
@@ -426,16 +426,16 @@
   // is created. Informs observers.
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterAdded(object_path));
+    for (auto& observer : observers_)
+      observer.AdapterAdded(object_path);
   }
 
   // Called by dbus::ObjectManager when an object with the adapter interface
   // is removed. Informs observers.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.AdapterRemoved(object_path);
   }
 
   // Called by dbus::PropertySet when a property value is changed,
@@ -443,8 +443,8 @@
   // call. Informs observers.
   void OnPropertyChanged(const dbus::ObjectPath& object_path,
                          const std::string& property_name) {
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterPropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.AdapterPropertyChanged(object_path, property_name);
   }
 
   // Called when a response for successful method call is received.
diff --git a/device/bluetooth/dbus/bluetooth_device_client.cc b/device/bluetooth/dbus/bluetooth_device_client.cc
index 7093089..1eec1ca 100644
--- a/device/bluetooth/dbus/bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/bluetooth_device_client.cc
@@ -465,16 +465,16 @@
   // is created. Informs observers.
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                      DeviceAdded(object_path));
+    for (auto& observer : observers_)
+      observer.DeviceAdded(object_path);
   }
 
   // Called by dbus::ObjectManager when an object with the device interface
   // is removed. Informs observers.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                      DeviceRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.DeviceRemoved(object_path);
   }
 
   // Called by BluetoothPropertySet when a property value is changed,
@@ -482,8 +482,8 @@
   // call. Informs observers.
   void OnPropertyChanged(const dbus::ObjectPath& object_path,
                          const std::string& property_name) {
-    FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                      DevicePropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.DevicePropertyChanged(object_path, property_name);
   }
 
   // Called when a response for successful method call is received.
diff --git a/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc b/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc
index 1ce7c35b..7423eb6 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc
@@ -202,16 +202,16 @@
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
     VLOG(2) << "Remote GATT characteristic added: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
-                      GattCharacteristicAdded(object_path));
+    for (auto& observer : observers_)
+      observer.GattCharacteristicAdded(object_path);
   }
 
   // dbus::ObjectManager::Interface override.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
     VLOG(2) << "Remote GATT characteristic removed: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
-                      GattCharacteristicRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.GattCharacteristicRemoved(object_path);
   }
 
  protected:
@@ -234,9 +234,8 @@
                                  const std::string& property_name) {
     VLOG(2) << "Remote GATT characteristic property changed: "
             << object_path.value() << ": " << property_name;
-    FOR_EACH_OBSERVER(
-        BluetoothGattCharacteristicClient::Observer, observers_,
-        GattCharacteristicPropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.GattCharacteristicPropertyChanged(object_path, property_name);
   }
 
   // Called when a response for successful method call is received.
diff --git a/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc b/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc
index e4b724c..d083dee 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc
@@ -160,16 +160,16 @@
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
     VLOG(2) << "Remote GATT descriptor added: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattDescriptorClient::Observer, observers_,
-                      GattDescriptorAdded(object_path));
+    for (auto& observer : observers_)
+      observer.GattDescriptorAdded(object_path);
   }
 
   // dbus::ObjectManager::Interface override.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
     VLOG(2) << "Remote GATT descriptor removed: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattDescriptorClient::Observer, observers_,
-                      GattDescriptorRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.GattDescriptorRemoved(object_path);
   }
 
  protected:
@@ -191,9 +191,8 @@
                                  const std::string& property_name) {
     VLOG(2) << "Remote GATT descriptor property changed: "
             << object_path.value() << ": " << property_name;
-    FOR_EACH_OBSERVER(
-        BluetoothGattDescriptorClient::Observer, observers_,
-        GattDescriptorPropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.GattDescriptorPropertyChanged(object_path, property_name);
   }
 
   // Called when a response for a successful method call is received.
diff --git a/device/bluetooth/dbus/bluetooth_gatt_service_client.cc b/device/bluetooth/dbus/bluetooth_gatt_service_client.cc
index bcc0fa8c..c27284f 100644
--- a/device/bluetooth/dbus/bluetooth_gatt_service_client.cc
+++ b/device/bluetooth/dbus/bluetooth_gatt_service_client.cc
@@ -81,16 +81,16 @@
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
     VLOG(2) << "Remote GATT service added: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                      GattServiceAdded(object_path));
+    for (auto& observer : observers_)
+      observer.GattServiceAdded(object_path);
   }
 
   // dbus::ObjectManager::Interface override.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
     VLOG(2) << "Remote GATT service removed: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                      GattServiceRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.GattServiceRemoved(object_path);
   }
 
  protected:
@@ -112,8 +112,8 @@
                                  const std::string& property_name) {
     VLOG(2) << "Remote GATT service property changed: " << object_path.value()
             << ": " << property_name;
-    FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                      GattServicePropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.GattServicePropertyChanged(object_path, property_name);
   }
 
   dbus::ObjectManager* object_manager_;
diff --git a/device/bluetooth/dbus/bluetooth_input_client.cc b/device/bluetooth/dbus/bluetooth_input_client.cc
index a116e7b..de5bdd04 100644
--- a/device/bluetooth/dbus/bluetooth_input_client.cc
+++ b/device/bluetooth/dbus/bluetooth_input_client.cc
@@ -83,16 +83,16 @@
   // is created. Informs observers.
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                      InputAdded(object_path));
+    for (auto& observer : observers_)
+      observer.InputAdded(object_path);
   }
 
   // Called by dbus::ObjectManager when an object with the input interface
   // is removed. Informs observers.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                      InputRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.InputRemoved(object_path);
   }
 
   // Called by BluetoothPropertySet when a property value is changed,
@@ -100,8 +100,8 @@
   // call. Informs observers.
   void OnPropertyChanged(const dbus::ObjectPath& object_path,
                          const std::string& property_name) {
-    FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                      InputPropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.InputPropertyChanged(object_path, property_name);
   }
 
   dbus::ObjectManager* object_manager_;
diff --git a/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc b/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc
index fcb39553..3488cc19 100644
--- a/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc
+++ b/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc
@@ -150,16 +150,16 @@
   // interface is created. Informs observers.
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothLEAdvertisingManagerClient::Observer, observers_,
-                      AdvertisingManagerAdded(object_path));
+    for (auto& observer : observers_)
+      observer.AdvertisingManagerAdded(object_path);
   }
 
   // Called by dbus::ObjectManager when an object with the advertising manager
   // interface is removed. Informs observers.
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
-    FOR_EACH_OBSERVER(BluetoothLEAdvertisingManagerClient::Observer, observers_,
-                      AdvertisingManagerRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.AdvertisingManagerRemoved(object_path);
   }
 
   // Called when a response for successful method call is received.
diff --git a/device/bluetooth/dbus/bluetooth_media_client.cc b/device/bluetooth/dbus/bluetooth_media_client.cc
index 3815bc87..3f6aa401 100644
--- a/device/bluetooth/dbus/bluetooth_media_client.cc
+++ b/device/bluetooth/dbus/bluetooth_media_client.cc
@@ -73,15 +73,15 @@
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
     VLOG(1) << "Remote Media added: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothMediaClient::Observer, observers_,
-                      MediaAdded(object_path));
+    for (auto& observer : observers_)
+      observer.MediaAdded(object_path);
   }
 
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
     VLOG(1) << "Remote Media removed: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothMediaClient::Observer, observers_,
-                      MediaRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.MediaRemoved(object_path);
   }
 
   // BluetoothMediaClient overrides.
diff --git a/device/bluetooth/dbus/bluetooth_media_transport_client.cc b/device/bluetooth/dbus/bluetooth_media_transport_client.cc
index 774d5e97..d6eb950 100644
--- a/device/bluetooth/dbus/bluetooth_media_transport_client.cc
+++ b/device/bluetooth/dbus/bluetooth_media_transport_client.cc
@@ -92,15 +92,15 @@
   void ObjectAdded(const dbus::ObjectPath& object_path,
                    const std::string& interface_name) override {
     VLOG(1) << "Remote Media Transport added: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothMediaTransportClient::Observer, observers_,
-                      MediaTransportAdded(object_path));
+    for (auto& observer : observers_)
+      observer.MediaTransportAdded(object_path);
   }
 
   void ObjectRemoved(const dbus::ObjectPath& object_path,
                      const std::string& interface_name) override {
     VLOG(1) << "Remote Media Transport removed: " << object_path.value();
-    FOR_EACH_OBSERVER(BluetoothMediaTransportClient::Observer, observers_,
-                      MediaTransportRemoved(object_path));
+    for (auto& observer : observers_)
+      observer.MediaTransportRemoved(object_path);
   }
 
   // BluetoothMediaTransportClient overrides.
@@ -206,9 +206,8 @@
     VLOG(1) << "Name of the changed property: " << property_name;
 
     // Dispatches the change to the corresponding property-changed handler.
-    FOR_EACH_OBSERVER(
-        BluetoothMediaTransportClient::Observer, observers_,
-        MediaTransportPropertyChanged(object_path, property_name));
+    for (auto& observer : observers_)
+      observer.MediaTransportPropertyChanged(object_path, property_name);
   }
 
   // Called when a response for successful method call is received.
diff --git a/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc b/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc
index cc8ce7d3..8d4478c 100644
--- a/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_adapter_client.cc
@@ -274,15 +274,15 @@
     // Adapter becoming visible
     visible_ = visible;
 
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterAdded(dbus::ObjectPath(kAdapterPath)));
+    for (auto& observer : observers_)
+      observer.AdapterAdded(dbus::ObjectPath(kAdapterPath));
 
   } else if (visible_ && !visible) {
     // Adapter becoming invisible
     visible_ = visible;
 
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterRemoved(dbus::ObjectPath(kAdapterPath)));
+    for (auto& observer : observers_)
+      observer.AdapterRemoved(dbus::ObjectPath(kAdapterPath));
   }
 }
 
@@ -291,15 +291,15 @@
     // Second adapter becoming visible
     second_visible_ = visible;
 
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterAdded(dbus::ObjectPath(kSecondAdapterPath)));
+    for (auto& observer : observers_)
+      observer.AdapterAdded(dbus::ObjectPath(kSecondAdapterPath));
 
   } else if (second_visible_ && !visible) {
     // Second adapter becoming invisible
     second_visible_ = visible;
 
-    FOR_EACH_OBSERVER(BluetoothAdapterClient::Observer, observers_,
-                      AdapterRemoved(dbus::ObjectPath(kSecondAdapterPath)));
+    for (auto& observer : observers_)
+      observer.AdapterRemoved(dbus::ObjectPath(kSecondAdapterPath));
   }
 }
 
@@ -329,9 +329,10 @@
     }
   }
 
-  FOR_EACH_OBSERVER(
-      BluetoothAdapterClient::Observer, observers_,
-      AdapterPropertyChanged(dbus::ObjectPath(kAdapterPath), property_name));
+  for (auto& observer : observers_) {
+    observer.AdapterPropertyChanged(dbus::ObjectPath(kAdapterPath),
+                                    property_name);
+  }
 }
 
 void FakeBluetoothAdapterClient::PostDelayedTask(
diff --git a/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
index 76875d1b..4d86797 100644
--- a/device/bluetooth/dbus/fake_bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
@@ -765,8 +765,8 @@
   properties_map_.insert(std::make_pair(device_path, std::move(properties)));
   device_list_.push_back(device_path);
 
-  FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                    DeviceAdded(device_path));
+  for (auto& observer : observers_)
+    observer.DeviceAdded(device_path);
 }
 
 void FakeBluetoothDeviceClient::CreateDeviceWithProperties(
@@ -799,8 +799,8 @@
   properties_map_.insert(std::make_pair(device_path, std::move(properties)));
   device_list_.push_back(device_path);
   pairing_options_map_.insert(std::make_pair(device_path, std::move(options)));
-  FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                    DeviceAdded(device_path));
+  for (auto& observer : observers_)
+    observer.DeviceAdded(device_path);
 }
 
 std::unique_ptr<base::ListValue>
@@ -1076,8 +1076,8 @@
     gatt_service_client->HideHeartRateService();
   }
 
-  FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                    DeviceRemoved(device_path));
+  for (auto& observer : observers_)
+    observer.DeviceRemoved(device_path);
 
   properties_map_.erase(iter);
   PairingOptionsMap::const_iterator options_iter =
@@ -1093,8 +1093,8 @@
     const std::string& property_name) {
   VLOG(2) << "Fake Bluetooth device property changed: " << object_path.value()
           << ": " << property_name;
-  FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                    DevicePropertyChanged(object_path, property_name));
+  for (auto& observer : observers_)
+    observer.DevicePropertyChanged(object_path, property_name);
 }
 
 void FakeBluetoothDeviceClient::DiscoverySimulationTimer() {
@@ -1790,8 +1790,8 @@
 
   properties_map_.insert(std::make_pair(device_path, std::move(properties)));
   device_list_.push_back(device_path);
-  FOR_EACH_OBSERVER(BluetoothDeviceClient::Observer, observers_,
-                    DeviceAdded(device_path));
+  for (auto& observer : observers_)
+    observer.DeviceAdded(device_path);
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc b/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc
index 5953c7e8..e677bcf 100644
--- a/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc
@@ -454,23 +454,22 @@
   VLOG(2) << "Characteristic property changed: " << object_path.value() << ": "
           << property_name;
 
-  FOR_EACH_OBSERVER(
-      BluetoothGattCharacteristicClient::Observer, observers_,
-      GattCharacteristicPropertyChanged(object_path, property_name));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicPropertyChanged(object_path, property_name);
 }
 
 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicAdded(
     const dbus::ObjectPath& object_path) {
   VLOG(2) << "GATT characteristic added: " << object_path.value();
-  FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
-                    GattCharacteristicAdded(object_path));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicAdded(object_path);
 }
 
 void FakeBluetoothGattCharacteristicClient::NotifyCharacteristicRemoved(
     const dbus::ObjectPath& object_path) {
   VLOG(2) << "GATT characteristic removed: " << object_path.value();
-  FOR_EACH_OBSERVER(BluetoothGattCharacteristicClient::Observer, observers_,
-                    GattCharacteristicRemoved(object_path));
+  for (auto& observer : observers_)
+    observer.GattCharacteristicRemoved(object_path);
 }
 
 void FakeBluetoothGattCharacteristicClient::
diff --git a/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc b/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc
index b32befc..92dd12a 100644
--- a/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.cc
@@ -196,20 +196,20 @@
   VLOG(2) << "Descriptor property changed: " << object_path.value() << ": "
           << property_name;
 
-  FOR_EACH_OBSERVER(BluetoothGattDescriptorClient::Observer, observers_,
-                    GattDescriptorPropertyChanged(object_path, property_name));
+  for (auto& observer : observers_)
+    observer.GattDescriptorPropertyChanged(object_path, property_name);
 }
 
 void FakeBluetoothGattDescriptorClient::NotifyDescriptorAdded(
     const dbus::ObjectPath& object_path) {
-  FOR_EACH_OBSERVER(BluetoothGattDescriptorClient::Observer, observers_,
-                    GattDescriptorAdded(object_path));
+  for (auto& observer : observers_)
+    observer.GattDescriptorAdded(object_path);
 }
 
 void FakeBluetoothGattDescriptorClient::NotifyDescriptorRemoved(
     const dbus::ObjectPath& object_path) {
-  FOR_EACH_OBSERVER(BluetoothGattDescriptorClient::Observer, observers_,
-                    GattDescriptorRemoved(object_path));
+  for (auto& observer : observers_)
+    observer.GattDescriptorRemoved(object_path);
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc b/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc
index 3c5591d..8ee318b 100644
--- a/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc
@@ -181,22 +181,22 @@
     const std::string& property_name) {
   VLOG(2) << "Fake GATT Service property changed: " << object_path.value()
           << ": " << property_name;
-  FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                    GattServicePropertyChanged(object_path, property_name));
+  for (auto& observer : observers_)
+    observer.GattServicePropertyChanged(object_path, property_name);
 }
 
 void FakeBluetoothGattServiceClient::NotifyServiceAdded(
     const dbus::ObjectPath& object_path) {
   VLOG(2) << "GATT service added: " << object_path.value();
-  FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                    GattServiceAdded(object_path));
+  for (auto& observer : observers_)
+    observer.GattServiceAdded(object_path);
 }
 
 void FakeBluetoothGattServiceClient::NotifyServiceRemoved(
     const dbus::ObjectPath& object_path) {
   VLOG(2) << "GATT service removed: " << object_path.value();
-  FOR_EACH_OBSERVER(BluetoothGattServiceClient::Observer, observers_,
-                    GattServiceRemoved(object_path));
+  for (auto& observer : observers_)
+    observer.GattServiceRemoved(object_path);
 }
 
 void FakeBluetoothGattServiceClient::ExposeHeartRateCharacteristics() {
diff --git a/device/bluetooth/dbus/fake_bluetooth_input_client.cc b/device/bluetooth/dbus/fake_bluetooth_input_client.cc
index bd3b86f6..36030626 100644
--- a/device/bluetooth/dbus/fake_bluetooth_input_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_input_client.cc
@@ -95,8 +95,8 @@
 
   properties_map_[object_path] = properties;
 
-  FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                    InputAdded(object_path));
+  for (auto& observer : observers_)
+    observer.InputAdded(object_path);
 }
 
 void FakeBluetoothInputClient::RemoveInputDevice(
@@ -106,8 +106,8 @@
   if (it == properties_map_.end())
     return;
 
-  FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                    InputRemoved(object_path));
+  for (auto& observer : observers_)
+    observer.InputRemoved(object_path);
 
   delete it->second;
   properties_map_.erase(it);
@@ -116,8 +116,8 @@
 void FakeBluetoothInputClient::OnPropertyChanged(
     const dbus::ObjectPath& object_path,
     const std::string& property_name) {
-  FOR_EACH_OBSERVER(BluetoothInputClient::Observer, observers_,
-                    InputPropertyChanged(object_path, property_name));
+  for (auto& observer : observers_)
+    observer.InputPropertyChanged(object_path, property_name);
 }
 
 }  // namespace bluez
diff --git a/device/bluetooth/dbus/fake_bluetooth_media_client.cc b/device/bluetooth/dbus/fake_bluetooth_media_client.cc
index 3b7a83b9..7ca3a7a8 100644
--- a/device/bluetooth/dbus/fake_bluetooth_media_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_media_client.cc
@@ -100,8 +100,8 @@
     SetEndpointRegistered(endpoints_.begin()->second, false);
 
   // Notifies observers about the change on |visible_|.
-  FOR_EACH_OBSERVER(BluetoothMediaClient::Observer, observers_,
-                    MediaRemoved(object_path_));
+  for (auto& observer : observers_)
+    observer.MediaRemoved(object_path_);
 }
 
 void FakeBluetoothMediaClient::SetEndpointRegistered(
diff --git a/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc b/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc
index f021ef0d..72f0362 100644
--- a/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc
@@ -193,8 +193,8 @@
   ObjectPath transport_path = transport->path;
 
   // Notifies observers about the state change of the transport.
-  FOR_EACH_OBSERVER(BluetoothMediaTransportClient::Observer, observers_,
-                    MediaTransportRemoved(transport_path));
+  for (auto& observer : observers_)
+    observer.MediaTransportRemoved(transport_path);
 
   endpoint->ClearConfiguration(transport_path);
   delete transport;
@@ -212,10 +212,10 @@
     return;
 
   transport->properties->state.ReplaceValue(state);
-  FOR_EACH_OBSERVER(
-      BluetoothMediaTransportClient::Observer, observers_,
-      MediaTransportPropertyChanged(
-          transport->path, BluetoothMediaTransportClient::kStateProperty));
+  for (auto& observer : observers_) {
+    observer.MediaTransportPropertyChanged(
+        transport->path, BluetoothMediaTransportClient::kStateProperty);
+  }
 }
 
 void FakeBluetoothMediaTransportClient::SetVolume(
@@ -226,10 +226,10 @@
     return;
 
   transport->properties->volume.ReplaceValue(volume);
-  FOR_EACH_OBSERVER(
-      BluetoothMediaTransportClient::Observer, observers_,
-      MediaTransportPropertyChanged(
-          transport->path, BluetoothMediaTransportClient::kVolumeProperty));
+  for (auto& observer : observers_) {
+    observer.MediaTransportPropertyChanged(
+        transport->path, BluetoothMediaTransportClient::kVolumeProperty);
+  }
 }
 
 void FakeBluetoothMediaTransportClient::WriteData(
diff --git a/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc
index 4123e2d..54726ae 100644
--- a/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc
+++ b/device/bluetooth/test/mock_bluetooth_gatt_notify_session.cc
@@ -40,9 +40,8 @@
     MockBluetoothAdapter* adapter,
     MockBluetoothGattCharacteristic* characteristic,
     const std::vector<uint8_t>& value) {
-  FOR_EACH_OBSERVER(
-      BluetoothAdapter::Observer, adapter->GetObservers(),
-      GattCharacteristicValueChanged(adapter, characteristic, value));
+  for (auto& observer : adapter->GetObservers())
+    observer.GattCharacteristicValueChanged(adapter, characteristic, value);
 }
 
 }  // namespace device
diff --git a/device/generic_sensor/platform_sensor.cc b/device/generic_sensor/platform_sensor.cc
index ced76bb..eb98f34 100644
--- a/device/generic_sensor/platform_sensor.cc
+++ b/device/generic_sensor/platform_sensor.cc
@@ -105,7 +105,8 @@
 }
 
 void PlatformSensor::NotifySensorError() {
-  FOR_EACH_OBSERVER(Client, clients_, OnSensorError());
+  for (auto& observer : clients_)
+    observer.OnSensorError();
 }
 
 bool PlatformSensor::UpdateSensorInternal(const ConfigMap& configurations) {
diff --git a/device/hid/hid_service.cc b/device/hid/hid_service.cc
index 09b2ab60..1e118ef 100644
--- a/device/hid/hid_service.cc
+++ b/device/hid/hid_service.cc
@@ -103,7 +103,8 @@
                   << device_info->device_id() << "'";
 
     if (enumeration_ready_) {
-      FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceAdded(device_info));
+      for (auto& observer : observer_list_)
+        observer.OnDeviceAdded(device_info);
     }
   }
 }
@@ -116,12 +117,13 @@
 
     scoped_refptr<HidDeviceInfo> device = it->second;
     if (enumeration_ready_) {
-      FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemoved(device));
+      for (auto& observer : observer_list_)
+        observer.OnDeviceRemoved(device);
     }
     devices_.erase(it);
     if (enumeration_ready_) {
-      FOR_EACH_OBSERVER(Observer, observer_list_,
-                        OnDeviceRemovedCleanup(device));
+      for (auto& observer : observer_list_)
+        observer.OnDeviceRemovedCleanup(device);
     }
   }
 }
diff --git a/device/hid/input_service_linux.cc b/device/hid/input_service_linux.cc
index 75eb352..b9a1ac0 100644
--- a/device/hid/input_service_linux.cc
+++ b/device/hid/input_service_linux.cc
@@ -241,12 +241,14 @@
 
 void InputServiceLinux::AddDevice(const InputDeviceInfo& info) {
   devices_[info.id] = info;
-  FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceAdded(info));
+  for (auto& observer : observers_)
+    observer.OnInputDeviceAdded(info);
 }
 
 void InputServiceLinux::RemoveDevice(const std::string& id) {
   devices_.erase(id);
-  FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceRemoved(id));
+  for (auto& observer : observers_)
+    observer.OnInputDeviceRemoved(id);
 }
 
 bool InputServiceLinux::CalledOnValidThread() const {
diff --git a/device/media_transfer_protocol/media_transfer_protocol_manager.cc b/device/media_transfer_protocol/media_transfer_protocol_manager.cc
index 5986456..9c07610 100644
--- a/device/media_transfer_protocol/media_transfer_protocol_manager.cc
+++ b/device/media_transfer_protocol/media_transfer_protocol_manager.cc
@@ -355,9 +355,8 @@
       // Return to avoid giving observers phantom detach events.
       return;
     }
-    FOR_EACH_OBSERVER(Observer,
-                      observers_,
-                      StorageChanged(false /* detach */, storage_name));
+    for (auto& observer : observers_)
+      observer.StorageChanged(false /* detach */, storage_name);
   }
 
   void OnStorageChanged(bool is_attach, const std::string& storage_name) {
@@ -398,9 +397,8 @@
 
     // New storage. Add it and let the observers know.
     storage_info_map_.insert(std::make_pair(storage_name, storage_info));
-    FOR_EACH_OBSERVER(Observer,
-                      observers_,
-                      StorageChanged(true /* is attach */, storage_name));
+    for (auto& observer : observers_)
+      observer.StorageChanged(true /* is attach */, storage_name);
   }
 
   void OnGetStorageInfoFromDevice(const MtpStorageInfo& storage_info) {
diff --git a/device/usb/usb_device.cc b/device/usb/usb_device.cc
index 06c4f7c14..5e25dcd 100644
--- a/device/usb/usb_device.cc
+++ b/device/usb/usb_device.cc
@@ -73,7 +73,8 @@
 }
 
 void UsbDevice::NotifyDeviceRemoved() {
-  FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemoved(this));
+  for (auto& observer : observer_list_)
+    observer.OnDeviceRemoved(this);
 }
 
 void UsbDevice::OnDisconnect() {
diff --git a/device/usb/usb_service.cc b/device/usb/usb_service.cc
index b6e6dca0..1a2cf98 100644
--- a/device/usb/usb_service.cc
+++ b/device/usb/usb_service.cc
@@ -53,7 +53,8 @@
 UsbService::~UsbService() {
   for (const auto& map_entry : devices_)
     map_entry.second->OnDisconnect();
-  FOR_EACH_OBSERVER(Observer, observer_list_, WillDestroyUsbService());
+  for (auto& observer : observer_list_)
+    observer.WillDestroyUsbService();
 }
 
 UsbService::UsbService(
@@ -127,15 +128,18 @@
 void UsbService::NotifyDeviceAdded(scoped_refptr<UsbDevice> device) {
   DCHECK(CalledOnValidThread());
 
-  FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceAdded(device));
+  for (auto& observer : observer_list_)
+    observer.OnDeviceAdded(device);
 }
 
 void UsbService::NotifyDeviceRemoved(scoped_refptr<UsbDevice> device) {
   DCHECK(CalledOnValidThread());
 
-  FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemoved(device));
+  for (auto& observer : observer_list_)
+    observer.OnDeviceRemoved(device);
   device->NotifyDeviceRemoved();
-  FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceRemovedCleanup(device));
+  for (auto& observer : observer_list_)
+    observer.OnDeviceRemovedCleanup(device);
 }
 
 }  // namespace device
diff --git a/extensions/renderer/extension_helper.cc b/extensions/renderer/extension_helper.cc
index f1371fb..461ac3e 100644
--- a/extensions/renderer/extension_helper.cc
+++ b/extensions/renderer/extension_helper.cc
@@ -73,8 +73,8 @@
       dispatcher_->script_context_set().GetByV8Context(v8_context);
   if (!script_context)
     return;
-  script_context->module_system()->CallModuleMethod("app.window",
-                                                    "onAppWindowClosed");
+  script_context->module_system()->CallModuleMethodSafe("app.window",
+                                                        "onAppWindowClosed");
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/json_schema_unittest.cc b/extensions/renderer/json_schema_unittest.cc
index b5e878fb..122f0397 100644
--- a/extensions/renderer/json_schema_unittest.cc
+++ b/extensions/renderer/json_schema_unittest.cc
@@ -25,7 +25,7 @@
 
  protected:
   void TestFunction(const std::string& test_name) {
-    env()->module_system()->CallModuleMethod("json_schema_test", test_name);
+    env()->module_system()->CallModuleMethodSafe("json_schema_test", test_name);
   }
 
  private:
diff --git a/extensions/renderer/module_system.cc b/extensions/renderer/module_system.cc
index b814350e..2fe422a 100644
--- a/extensions/renderer/module_system.cc
+++ b/extensions/renderer/module_system.cc
@@ -314,39 +314,14 @@
   v8::Local<v8::Context> v8_context = context()->v8_context();
   v8::Context::Scope context_scope(v8_context);
 
-  v8::Local<v8::String> v8_module_name;
-  v8::Local<v8::String> v8_method_name;
-  if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name) ||
-      !ToV8String(GetIsolate(), method_name.c_str(), &v8_method_name)) {
-    return handle_scope.Escape(v8::Undefined(GetIsolate()));
-  }
+  v8::Local<v8::Function> function =
+      GetModuleFunction(module_name, method_name);
 
-  v8::Local<v8::Value> module;
-  {
-    NativesEnabledScope natives_enabled(this);
-    module = RequireForJsInner(v8_module_name);
-  }
-
-  if (module.IsEmpty() || !module->IsObject()) {
-    Fatal(context_,
-          "Failed to get module " + module_name + " to call " + method_name);
-    return handle_scope.Escape(v8::Undefined(GetIsolate()));
-  }
-
-  v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(module);
-  v8::Local<v8::Value> value;
-  if (!GetProperty(v8_context, object, v8_method_name, &value) ||
-      !value->IsFunction()) {
-    Fatal(context_, module_name + "." + method_name + " is not a function");
-    return handle_scope.Escape(v8::Undefined(GetIsolate()));
-  }
-
-  v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(value);
   v8::Local<v8::Value> result;
   {
     v8::TryCatch try_catch(GetIsolate());
     try_catch.SetCaptureMessage(true);
-    result = context_->CallFunction(func, argc, argv);
+    result = context_->CallFunction(function, argc, argv);
     if (try_catch.HasCaught()) {
       HandleException(try_catch);
       result = v8::Undefined(GetIsolate());
@@ -355,6 +330,43 @@
   return handle_scope.Escape(result);
 }
 
+void ModuleSystem::CallModuleMethodSafe(const std::string& module_name,
+                                        const std::string& method_name) {
+  v8::HandleScope handle_scope(GetIsolate());
+  v8::Local<v8::Value> no_args;
+  CallModuleMethodSafe(module_name, method_name, 0, &no_args);
+}
+
+void ModuleSystem::CallModuleMethodSafe(
+    const std::string& module_name,
+    const std::string& method_name,
+    std::vector<v8::Local<v8::Value>>* args) {
+  CallModuleMethodSafe(module_name, method_name, args->size(), args->data());
+}
+
+void ModuleSystem::CallModuleMethodSafe(const std::string& module_name,
+                                        const std::string& method_name,
+                                        int argc,
+                                        v8::Local<v8::Value> argv[]) {
+  TRACE_EVENT2("v8", "v8.callModuleMethodSafe", "module_name", module_name,
+               "method_name", method_name);
+
+  v8::HandleScope handle_scope(GetIsolate());
+  v8::Local<v8::Context> v8_context = context()->v8_context();
+  v8::Context::Scope context_scope(v8_context);
+
+  v8::Local<v8::Function> function =
+      GetModuleFunction(module_name, method_name);
+
+  {
+    v8::TryCatch try_catch(GetIsolate());
+    try_catch.SetCaptureMessage(true);
+    context_->SafeCallFunction(function, argc, argv);
+    if (try_catch.HasCaught())
+      HandleException(try_catch);
+  }
+}
+
 void ModuleSystem::RegisterNativeHandler(
     const std::string& name,
     std::unique_ptr<NativeHandler> native_handler) {
@@ -781,4 +793,39 @@
   }
 }
 
+v8::Local<v8::Function> ModuleSystem::GetModuleFunction(
+    const std::string& module_name,
+    const std::string& method_name) {
+  v8::Local<v8::String> v8_module_name;
+  v8::Local<v8::String> v8_method_name;
+  v8::Local<v8::Function> function;
+  if (!ToV8String(GetIsolate(), module_name.c_str(), &v8_module_name) ||
+      !ToV8String(GetIsolate(), method_name.c_str(), &v8_method_name)) {
+    return function;
+  }
+
+  v8::Local<v8::Value> module;
+  {
+    NativesEnabledScope natives_enabled(this);
+    module = RequireForJsInner(v8_module_name);
+  }
+
+  if (module.IsEmpty() || !module->IsObject()) {
+    Fatal(context_,
+          "Failed to get module " + module_name + " to call " + method_name);
+    return function;
+  }
+
+  v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(module);
+  v8::Local<v8::Value> value;
+  if (!GetProperty(context()->v8_context(), object, v8_method_name, &value) ||
+      !value->IsFunction()) {
+    Fatal(context_, module_name + "." + method_name + " is not a function");
+    return function;
+  }
+
+  function = v8::Local<v8::Function>::Cast(value);
+  return function;
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/module_system.h b/extensions/renderer/module_system.h
index 4ce5458..1797f9c 100644
--- a/extensions/renderer/module_system.h
+++ b/extensions/renderer/module_system.h
@@ -91,8 +91,11 @@
 
   // Calls the specified method exported by the specified module. This is
   // equivalent to calling require('module_name').method_name() from JS.
-  v8::Local<v8::Value> CallModuleMethod(const std::string& module_name,
-                                        const std::string& method_name);
+  // DEPRECATED: see crbug.com/629431
+  // TODO(devlin): Remove these.
+  v8::Local<v8::Value> CallModuleMethod(
+      const std::string& module_name,
+      const std::string& method_name);
   v8::Local<v8::Value> CallModuleMethod(
       const std::string& module_name,
       const std::string& method_name,
@@ -102,6 +105,17 @@
                                         int argc,
                                         v8::Local<v8::Value> argv[]);
 
+  // Same as the above, but allows for blocking execution.
+  void CallModuleMethodSafe(const std::string& module_name,
+                            const std::string& method_name);
+  void CallModuleMethodSafe(const std::string& module_name,
+                            const std::string& method_name,
+                            std::vector<v8::Local<v8::Value>>* args);
+  void CallModuleMethodSafe(const std::string& module_name,
+                            const std::string& method_name,
+                            int argc,
+                            v8::Local<v8::Value> argv[]);
+
   // Register |native_handler| as a potential target for requireNative(), so
   // calls to requireNative(|name|) from JS will return a new object created by
   // |native_handler|.
@@ -218,6 +232,10 @@
   // See |clobbered_native_handlers_|.
   void ClobberExistingNativeHandler(const std::string& name);
 
+  // Returns the v8::Function associated with the given module and method name.
+  v8::Local<v8::Function> GetModuleFunction(const std::string& module_name,
+                                            const std::string& method_name);
+
   ScriptContext* context_;
 
   // A map from module names to the JS source for that module. GetSource()
diff --git a/extensions/renderer/utils_unittest.cc b/extensions/renderer/utils_unittest.cc
index 485c214..b45c359 100644
--- a/extensions/renderer/utils_unittest.cc
+++ b/extensions/renderer/utils_unittest.cc
@@ -38,38 +38,39 @@
 TEST_F(UtilsUnittest, SuperClass) {
   ModuleSystem::NativesEnabledScope natives_enabled_scope(
       env()->module_system());
-  env()->module_system()->CallModuleMethod("utils_unittest", "testSuperClass");
+  env()->module_system()->CallModuleMethodSafe("utils_unittest",
+                                               "testSuperClass");
 }
 
 TEST_F(UtilsUnittest, PromiseNoResult) {
   ModuleSystem::NativesEnabledScope natives_enabled_scope(
       env()->module_system());
-  env()->module_system()->CallModuleMethod("utils_unittest",
-                                           "testPromiseNoResult");
+  env()->module_system()->CallModuleMethodSafe("utils_unittest",
+                                               "testPromiseNoResult");
   RunResolvedPromises();
 }
 
 TEST_F(UtilsUnittest, PromiseOneResult) {
   ModuleSystem::NativesEnabledScope natives_enabled_scope(
       env()->module_system());
-  env()->module_system()->CallModuleMethod("utils_unittest",
-                                           "testPromiseOneResult");
+  env()->module_system()->CallModuleMethodSafe("utils_unittest",
+                                               "testPromiseOneResult");
   RunResolvedPromises();
 }
 
 TEST_F(UtilsUnittest, PromiseTwoResults) {
   ModuleSystem::NativesEnabledScope natives_enabled_scope(
       env()->module_system());
-  env()->module_system()->CallModuleMethod("utils_unittest",
-                                           "testPromiseTwoResults");
+  env()->module_system()->CallModuleMethodSafe("utils_unittest",
+                                               "testPromiseTwoResults");
   RunResolvedPromises();
 }
 
 TEST_F(UtilsUnittest, PromiseError) {
   ModuleSystem::NativesEnabledScope natives_enabled_scope(
       env()->module_system());
-  env()->module_system()->CallModuleMethod("utils_unittest",
-                                           "testPromiseError");
+  env()->module_system()->CallModuleMethodSafe("utils_unittest",
+                                               "testPromiseError");
   RunResolvedPromises();
 }
 
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index af5ae02..d8745bc4 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -5,13 +5,13 @@
   project_id: "build"
   url: "https://chromium.googlesource.com/chromium/tools/build.git"
   branch: "master"
-  revision: "5993e5340c3d0e30c1c07528f424177cf7c83acc"
+  revision: "ece187bdc4c389344de942753c06aaa69d0e880e"
 }
 deps {
   project_id: "depot_tools"
   url: "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
   branch: "master"
-  revision: "1c822ade1f731ec35a3d2d3be8805f773df22c4c"
+  revision: "78de30e1bb2a3065ea78c28305be6782cc7c4e43"
 }
 deps {
   project_id: "recipe_engine"
diff --git a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64___ignition_fail.json b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64___ignition_fail.json
index 06eea344..38b2c57 100644
--- a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64___ignition_fail.json
+++ b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64___ignition_fail.json
@@ -429,6 +429,23 @@
     ]
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_PACKAGE_REPO[build]/scripts/slave/chromium/archive_layout_test_retry_summary.py",
+      "--retry-summary-json",
+      "{\"failures\": [], \"ignored\": [\"bad/totally-bad-probably.html\", \"tricky/totally-maybe-not-awesome.html\"]}",
+      "--build-number",
+      "571",
+      "--builder-name",
+      "V8-Blink Linux 64 - ignition",
+      "--gs-bucket",
+      "gs://chromium-layout-test-archives"
+    ],
+    "cwd": "[BUILDER_CACHE]/V8_Blink_Linux_64___ignition",
+    "name": "archive_retry_summary"
+  },
+  {
     "name": "$result",
     "recipe_result": null,
     "status_code": 0
diff --git a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64__dbg__fail.json b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64__dbg__fail.json
index c884de0..aec8b98 100644
--- a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64__dbg__fail.json
+++ b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64__dbg__fail.json
@@ -579,6 +579,23 @@
     ]
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_PACKAGE_REPO[build]/scripts/slave/chromium/archive_layout_test_retry_summary.py",
+      "--retry-summary-json",
+      "{\"failures\": [], \"ignored\": [\"bad/totally-bad-probably.html\", \"tricky/totally-maybe-not-awesome.html\"]}",
+      "--build-number",
+      "571",
+      "--builder-name",
+      "V8-Blink Linux 64 (dbg)",
+      "--gs-bucket",
+      "gs://chromium-layout-test-archives"
+    ],
+    "cwd": "[BUILDER_CACHE]/V8_Blink_Linux_64__dbg_",
+    "name": "archive_retry_summary"
+  },
+  {
     "name": "$result",
     "recipe_result": null,
     "status_code": 0
diff --git a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64_fail.json b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64_fail.json
index 40d2886..8fb04676 100644
--- a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64_fail.json
+++ b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Linux_64_fail.json
@@ -579,6 +579,23 @@
     ]
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_PACKAGE_REPO[build]/scripts/slave/chromium/archive_layout_test_retry_summary.py",
+      "--retry-summary-json",
+      "{\"failures\": [], \"ignored\": [\"bad/totally-bad-probably.html\", \"tricky/totally-maybe-not-awesome.html\"]}",
+      "--build-number",
+      "571",
+      "--builder-name",
+      "V8-Blink Linux 64",
+      "--gs-bucket",
+      "gs://chromium-layout-test-archives"
+    ],
+    "cwd": "[BUILDER_CACHE]/V8_Blink_Linux_64",
+    "name": "archive_retry_summary"
+  },
+  {
     "name": "$result",
     "recipe_result": null,
     "status_code": 0
diff --git a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Mac_fail.json b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Mac_fail.json
index 84fc62b..b2f7c9b 100644
--- a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Mac_fail.json
+++ b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Mac_fail.json
@@ -629,6 +629,23 @@
     ]
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_PACKAGE_REPO[build]/scripts/slave/chromium/archive_layout_test_retry_summary.py",
+      "--retry-summary-json",
+      "{\"failures\": [], \"ignored\": [\"bad/totally-bad-probably.html\", \"tricky/totally-maybe-not-awesome.html\"]}",
+      "--build-number",
+      "571",
+      "--builder-name",
+      "V8-Blink Mac",
+      "--gs-bucket",
+      "gs://chromium-layout-test-archives"
+    ],
+    "cwd": "[BUILDER_CACHE]/V8_Blink_Mac",
+    "name": "archive_retry_summary"
+  },
+  {
     "name": "$result",
     "recipe_result": null,
     "status_code": 0
diff --git a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Win_fail.json b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Win_fail.json
index 0d6a3a8..5ebbbb7 100644
--- a/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Win_fail.json
+++ b/infra/recipes/blink_downstream.expected/full_client_v8_fyi_V8_Blink_Win_fail.json
@@ -558,6 +558,23 @@
     ]
   },
   {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_PACKAGE_REPO[build]\\scripts\\slave\\chromium\\archive_layout_test_retry_summary.py",
+      "--retry-summary-json",
+      "{\"failures\": [], \"ignored\": [\"bad/totally-bad-probably.html\", \"tricky/totally-maybe-not-awesome.html\"]}",
+      "--build-number",
+      "571",
+      "--builder-name",
+      "V8-Blink Win",
+      "--gs-bucket",
+      "gs://chromium-layout-test-archives"
+    ],
+    "cwd": "[BUILDER_CACHE]\\V8_Blink_Win",
+    "name": "archive_retry_summary"
+  },
+  {
     "name": "$result",
     "recipe_result": null,
     "status_code": 0
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index 35a898d..136f5bc 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -60,6 +60,8 @@
     "audio_device_name.h",
     "audio_device_thread.cc",
     "audio_device_thread.h",
+    "audio_features.cc",
+    "audio_features.h",
     "audio_input_controller.cc",
     "audio_input_controller.h",
     "audio_input_device.cc",
diff --git a/media/audio/audio_features.cc b/media/audio/audio_features.cc
new file mode 100644
index 0000000..5a5ead4
--- /dev/null
+++ b/media/audio/audio_features.cc
@@ -0,0 +1,16 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/audio/audio_features.h"
+
+namespace features {
+
+#if defined(OS_CHROMEOS)
+// Allows experimentally enables mediaDevices.enumerateDevices() on ChromeOS.
+// Default disabled (crbug.com/554168).
+const base::Feature kEnumerateAudioDevices{"EnumerateAudioDevices",
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+}  // namespace features
diff --git a/media/audio/audio_features.h b/media/audio/audio_features.h
new file mode 100644
index 0000000..6fc3946
--- /dev/null
+++ b/media/audio/audio_features.h
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_AUDIO_AUDIO_FEATURES_H_
+#define MEDIA_AUDIO_AUDIO_FEATURES_H_
+
+#include "base/feature_list.h"
+#include "build/build_config.h"
+#include "media/base/media_export.h"
+
+namespace features {
+
+#if defined(OS_CHROMEOS)
+MEDIA_EXPORT extern const base::Feature kEnumerateAudioDevices;
+#endif  // defined(OS_CHROMEOS)
+
+}  // features
+
+#endif  // MEDIA_AUDIO_AUDIO_FEATURES_H_
diff --git a/media/audio/audio_manager_unittest.cc b/media/audio/audio_manager_unittest.cc
index 5cfaf6a..d1b8dd6 100644
--- a/media/audio/audio_manager_unittest.cc
+++ b/media/audio/audio_manager_unittest.cc
@@ -364,7 +364,8 @@
 };
 
 #if defined(USE_CRAS)
-TEST_F(AudioManagerTest, EnumerateInputDevicesCras) {
+// TODO(warx): enable the test once crbug.com/554168 is fixed.
+TEST_F(AudioManagerTest, DISABLED_EnumerateInputDevicesCras) {
   // Setup the devices without internal mic, so that it doesn't exist
   // beamforming capable mic.
   AudioNodeList audio_nodes;
@@ -393,7 +394,8 @@
   CheckDeviceNamesCras(device_names, expectation);
 }
 
-TEST_F(AudioManagerTest, EnumerateOutputDevicesCras) {
+// TODO(warx): enable the test once crbug.com/554168 is fixed.
+TEST_F(AudioManagerTest, DISABLED_EnumerateOutputDevicesCras) {
   // Setup the devices without internal mic, so that it doesn't exist
   // beamforming capable mic.
   AudioNodeList audio_nodes;
diff --git a/media/audio/cras/audio_manager_cras.cc b/media/audio/cras/audio_manager_cras.cc
index a2f0cd3..6df14bc87 100644
--- a/media/audio/cras/audio_manager_cras.cc
+++ b/media/audio/cras/audio_manager_cras.cc
@@ -19,6 +19,7 @@
 #include "chromeos/audio/audio_device.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "media/audio/audio_device_description.h"
+#include "media/audio/audio_features.h"
 #include "media/audio/cras/cras_input.h"
 #include "media/audio/cras/cras_unified.h"
 #include "media/base/channel_layout.h"
@@ -159,12 +160,15 @@
     AddBeamformingDevices(device_names);
   else
     device_names->push_back(media::AudioDeviceName::CreateDefault());
-  chromeos::AudioDeviceList devices;
-  chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices);
-  for (const auto& device : devices) {
-    if (device.is_input == is_input && device.is_for_simple_usage()) {
-      device_names->emplace_back(device.display_name,
-                                 base::Uint64ToString(device.id));
+
+  if (base::FeatureList::IsEnabled(features::kEnumerateAudioDevices)) {
+    chromeos::AudioDeviceList devices;
+    chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices);
+    for (const auto& device : devices) {
+      if (device.is_input == is_input && device.is_for_simple_usage()) {
+        device_names->emplace_back(device.display_name,
+                                   base::Uint64ToString(device.id));
+      }
     }
   }
 }
diff --git a/media/base/android/sdk_media_codec_bridge.cc b/media/base/android/sdk_media_codec_bridge.cc
index 3247dd8c..609370b 100644
--- a/media/base/android/sdk_media_codec_bridge.cc
+++ b/media/base/android/sdk_media_codec_bridge.cc
@@ -560,13 +560,16 @@
 }
 
 // static
-VideoCodecBridge* VideoCodecBridge::CreateDecoder(const VideoCodec& codec,
-                                                  bool is_secure,
-                                                  const gfx::Size& size,
-                                                  jobject surface,
-                                                  jobject media_crypto,
-                                                  bool allow_adaptive_playback,
-                                                  bool require_software_codec) {
+VideoCodecBridge* VideoCodecBridge::CreateDecoder(
+    const VideoCodec& codec,
+    bool is_secure,
+    const gfx::Size& size,
+    jobject surface,
+    jobject media_crypto,
+    const std::vector<uint8_t>& csd0,
+    const std::vector<uint8_t>& csd1,
+    bool allow_adaptive_playback,
+    bool require_software_codec) {
   if (!MediaCodecUtil::IsMediaCodecAvailable())
     return nullptr;
 
@@ -585,6 +588,19 @@
       Java_MediaCodecBridge_createVideoDecoderFormat(env, j_mime, size.width(),
                                                      size.height()));
   DCHECK(!j_format.is_null());
+
+  if (!csd0.empty()) {
+    ScopedJavaLocalRef<jbyteArray> j_csd0 =
+        base::android::ToJavaByteArray(env, csd0.data(), csd0.size());
+    Java_MediaCodecBridge_setCodecSpecificData(env, j_format, 0, j_csd0);
+  }
+
+  if (!csd1.empty()) {
+    ScopedJavaLocalRef<jbyteArray> j_csd1 =
+        base::android::ToJavaByteArray(env, csd1.data(), csd1.size());
+    Java_MediaCodecBridge_setCodecSpecificData(env, j_format, 1, j_csd1);
+  }
+
   if (!Java_MediaCodecBridge_configureVideo(env, bridge->media_codec(),
                                             j_format, surface, media_crypto, 0,
                                             allow_adaptive_playback)) {
diff --git a/media/base/android/sdk_media_codec_bridge.h b/media/base/android/sdk_media_codec_bridge.h
index 9be4cbc..82684af8 100644
--- a/media/base/android/sdk_media_codec_bridge.h
+++ b/media/base/android/sdk_media_codec_bridge.h
@@ -136,14 +136,17 @@
 
   // Create, start, and return a VideoCodecBridge decoder or NULL on failure.
   static VideoCodecBridge* CreateDecoder(
-      const VideoCodec& codec,  // e.g. media::kCodecVP8
-      bool is_secure,           // Will be used with encrypted content.
-      const gfx::Size& size,    // Output frame size.
-      jobject surface,          // Output surface, optional.
-      jobject media_crypto,     // MediaCrypto object, optional.
-      bool allow_adaptive_playback =
-          true,  // Should adaptive playback be allowed if supported.
-      bool require_software_codec = false);  // Require software decoder?
+      const VideoCodec& codec,
+      bool is_secure,         // Will be used with encrypted content.
+      const gfx::Size& size,  // Output frame size.
+      jobject surface,        // Output surface, optional.
+      jobject media_crypto,   // MediaCrypto object, optional.
+      // Codec specific data. See MediaCodec docs.
+      const std::vector<uint8_t>& csd0,
+      const std::vector<uint8_t>& csd1,
+      // Should adaptive playback be allowed if supported.
+      bool allow_adaptive_playback = true,
+      bool require_software_codec = false);
 
   // Create, start, and return a VideoCodecBridge encoder or NULL on failure.
   static VideoCodecBridge* CreateEncoder(
diff --git a/media/base/android/sdk_media_codec_bridge_unittest.cc b/media/base/android/sdk_media_codec_bridge_unittest.cc
index f2439d8..c697d74 100644
--- a/media/base/android/sdk_media_codec_bridge_unittest.cc
+++ b/media/base/android/sdk_media_codec_bridge_unittest.cc
@@ -153,7 +153,8 @@
 
   std::unique_ptr<media::MediaCodecBridge> media_codec;
   media_codec.reset(VideoCodecBridge::CreateDecoder(
-      kCodecH264, false, gfx::Size(640, 480), nullptr, nullptr));
+      kCodecH264, false, gfx::Size(640, 480), nullptr, nullptr,
+      std::vector<uint8_t>(), std::vector<uint8_t>()));
 }
 
 TEST(SdkMediaCodecBridgeTest, DoNormal) {
@@ -274,7 +275,8 @@
   SKIP_TEST_IF_VP8_DECODER_IS_NOT_SUPPORTED();
 
   std::unique_ptr<VideoCodecBridge> media_codec(VideoCodecBridge::CreateDecoder(
-      kCodecVP8, false, gfx::Size(320, 240), nullptr, nullptr));
+      kCodecVP8, false, gfx::Size(320, 240), nullptr, nullptr,
+      std::vector<uint8_t>(), std::vector<uint8_t>()));
   EXPECT_TRUE(media_codec.get());
   scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vp8-I-frame-320x240");
   DecodeMediaFrame(media_codec.get(), buffer->data(), buffer->data_size(),
@@ -299,9 +301,10 @@
 
 TEST(SdkMediaCodecBridgeTest, CreateUnsupportedCodec) {
   EXPECT_EQ(nullptr, AudioCodecBridge::Create(kUnknownAudioCodec));
-  EXPECT_EQ(nullptr, VideoCodecBridge::CreateDecoder(kUnknownVideoCodec, false,
-                                                     gfx::Size(320, 240),
-                                                     nullptr, nullptr));
+  EXPECT_EQ(nullptr,
+            VideoCodecBridge::CreateDecoder(
+                kUnknownVideoCodec, false, gfx::Size(320, 240), nullptr,
+                nullptr, std::vector<uint8_t>(), std::vector<uint8_t>()));
 }
 
 }  // namespace media
diff --git a/media/blink/cdm_result_promise_helper.cc b/media/blink/cdm_result_promise_helper.cc
index c96121a0..5d0a5e5 100644
--- a/media/blink/cdm_result_promise_helper.cc
+++ b/media/blink/cdm_result_promise_helper.cc
@@ -38,16 +38,21 @@
       return blink::WebContentDecryptionModuleExceptionNotSupportedError;
     case MediaKeys::INVALID_STATE_ERROR:
       return blink::WebContentDecryptionModuleExceptionInvalidStateError;
+
+    // TODO(jrummell): Since InvalidAccess is not returned, thus should be
+    // renamed to TYPE_ERROR. http://crbug.com/570216#c11.
     case MediaKeys::INVALID_ACCESS_ERROR:
-      return blink::WebContentDecryptionModuleExceptionInvalidAccessError;
+      return blink::WebContentDecryptionModuleExceptionTypeError;
     case MediaKeys::QUOTA_EXCEEDED_ERROR:
       return blink::WebContentDecryptionModuleExceptionQuotaExceededError;
     case MediaKeys::UNKNOWN_ERROR:
       return blink::WebContentDecryptionModuleExceptionUnknownError;
+
+    // These are deprecated, and should be removed.
+    // http://crbug.com/570216#c11.
     case MediaKeys::CLIENT_ERROR:
-      return blink::WebContentDecryptionModuleExceptionClientError;
     case MediaKeys::OUTPUT_ERROR:
-      return blink::WebContentDecryptionModuleExceptionOutputError;
+      break;
   }
   NOTREACHED();
   return blink::WebContentDecryptionModuleExceptionUnknownError;
diff --git a/media/blink/webcontentdecryptionmodulesession_impl.cc b/media/blink/webcontentdecryptionmodulesession_impl.cc
index 6308ade5..3bef8a9 100644
--- a/media/blink/webcontentdecryptionmodulesession_impl.cc
+++ b/media/blink/webcontentdecryptionmodulesession_impl.cc
@@ -256,10 +256,10 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // From https://w3c.github.io/encrypted-media/#generateRequest.
-  // 5. If the Key System implementation represented by this object's cdm
+  // 6. If the Key System implementation represented by this object's cdm
   //    implementation value does not support initDataType as an Initialization
-  //    Data Type, return a promise rejected with a new DOMException whose name
-  //    is NotSupportedError. String comparison is case-sensitive.
+  //    Data Type, return a promise rejected with a NotSupportedError.
+  //    String comparison is case-sensitive.
   EmeInitDataType eme_init_data_type = ConvertToEmeInitDataType(init_data_type);
   if (!IsSupportedKeySystemWithInitDataType(adapter_->GetKeySystem(),
                                             eme_init_data_type)) {
@@ -271,39 +271,50 @@
     return;
   }
 
-  // 9.1 If the init data is not valid for initDataType, reject promise with a
-  //     new DOMException whose name is InvalidAccessError.
-  // 9.2 Let sanitized init data be a validated and sanitized version of init
-  //     data. The user agent must thoroughly validate the Initialization Data
-  //     before passing it to the CDM. This includes verifying that the length
-  //     and values of fields are reasonable, verifying that values are within
-  //     reasonable limits, and stripping irrelevant, unsupported, or unknown
-  //     data or fields. It is recommended that user agents pre-parse, sanitize,
-  //     and/or generate a fully sanitized version of the Initialization Data.
-  //     If the Initialization Data format specified by initDataType support
-  //     multiple entries, the user agent should remove entries that are not
-  //     needed by the CDM.
-  // 9.3 If the previous step failed, reject promise with a new DOMException
-  //     whose name is InvalidAccessError.
+  // 10.1 If the init data is not valid for initDataType, reject promise with
+  //      a newly created TypeError.
+  // 10.2 Let sanitized init data be a validated and sanitized version of init
+  //      data. The user agent must thoroughly validate the Initialization Data
+  //      before passing it to the CDM. This includes verifying that the length
+  //      and values of fields are reasonable, verifying that values are within
+  //      reasonable limits, and stripping irrelevant, unsupported, or unknown
+  //      data or fields. It is recommended that user agents pre-parse,
+  //      sanitize, and/or generate a fully sanitized version of the
+  //      Initialization Data. If the Initialization Data format specified by
+  //      initDataType supports multiple entries, the user agent should remove
+  //      entries that are not needed by the CDM. The user agent must not
+  //      re-order entries within the Initialization Data.
+  // 10.3 If the preceding step failed, reject promise with a newly created
+  //      TypeError.
   std::vector<uint8_t> sanitized_init_data;
   std::string message;
   if (!SanitizeInitData(eme_init_data_type, init_data, init_data_length,
                         &sanitized_init_data, &message)) {
     result.completeWithError(
-        blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
+        blink::WebContentDecryptionModuleExceptionTypeError, 0,
         blink::WebString::fromUTF8(message));
     return;
   }
 
-  // 9.4 Let session id be the empty string.
-  //     (Done in constructor.)
+  // 10.4 If sanitized init data is empty, reject promise with a
+  //      NotSupportedError.
+  if (sanitized_init_data.empty()) {
+    result.completeWithError(
+        blink::WebContentDecryptionModuleExceptionNotSupportedError, 0,
+        "No initialization data provided.");
+    return;
+  }
 
-  // 9.5 Let message be null.
-  //     (Done by CDM.)
+  // 10.5 Let session id be the empty string.
+  //      (Done in constructor.)
 
-  // 9.6 Let cdm be the CDM instance represented by this object's cdm
-  //     instance value.
-  // 9.7 Use the cdm to execute the following steps:
+  // 10.6 Let message be null.
+  // 10.7 Let message type be null.
+  //      (Done by CDM.)
+
+  // 10.8 Let cdm be the CDM instance represented by this object's cdm
+  //      instance value.
+  // 10.9 Use the cdm to execute the following steps:
   adapter_->InitializeNewSession(
       eme_init_data_type, sanitized_init_data, convertSessionType(session_type),
       std::unique_ptr<NewSessionCdmPromise>(new NewSessionCdmResultPromise(
@@ -320,10 +331,17 @@
   DCHECK(session_id_.empty());
   DCHECK(thread_checker_.CalledOnValidThread());
 
+  // From https://w3c.github.io/encrypted-media/#load.
+  // 8.1 Let sanitized session ID be a validated and/or sanitized version of
+  //     sessionId. The user agent should thoroughly validate the sessionId
+  //     value before passing it to the CDM. At a minimum, this should include
+  //     checking that the length and value (e.g. alphanumeric) are reasonable.
+  // 8.2 If the preceding step failed, or if sanitized session ID is empty,
+  //     reject promise with a newly created TypeError.
   std::string sanitized_session_id;
   if (!SanitizeSessionId(session_id, &sanitized_session_id)) {
     result.completeWithError(
-        blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
+        blink::WebContentDecryptionModuleExceptionTypeError, 0,
         "Invalid session ID.");
     return;
   }
@@ -348,11 +366,21 @@
   DCHECK(!session_id_.empty());
   DCHECK(thread_checker_.CalledOnValidThread());
 
+  // From https://w3c.github.io/encrypted-media/#update.
+  // 6.1 Let sanitized response be a validated and/or sanitized version of
+  //     response copy. The user agent should thoroughly validate the response
+  //     before passing it to the CDM. This may include verifying values are
+  //     within reasonable limits, stripping irrelevant data or fields,
+  //     pre-parsing it, sanitizing it, and/or generating a fully sanitized
+  //     version. The user agent should check that the length and values of
+  //     fields are reasonable. Unknown fields should be rejected or removed.
+  // 6.2 If the preceding step failed, or if sanitized response is empty,
+  //     reject promise with a newly created TypeError.
   std::vector<uint8_t> sanitized_response;
   if (!SanitizeResponse(adapter_->GetKeySystem(), response, response_length,
                         &sanitized_response)) {
     result.completeWithError(
-        blink::WebContentDecryptionModuleExceptionInvalidAccessError, 0,
+        blink::WebContentDecryptionModuleExceptionTypeError, 0,
         "Invalid response.");
     return;
   }
diff --git a/media/blink/webencryptedmediaclient_impl.cc b/media/blink/webencryptedmediaclient_impl.cc
index 6ad39772..91e6303e 100644
--- a/media/blink/webencryptedmediaclient_impl.cc
+++ b/media/blink/webencryptedmediaclient_impl.cc
@@ -138,9 +138,21 @@
   GetReporter(request.keySystem())->ReportSupported();
   // TODO(sandersd): Pass |are_secure_codecs_required| along and use it to
   // configure the CDM security level and use of secure surfaces on Android.
+
+  // If the frame is closed while the permission prompt is displayed,
+  // the permission prompt is dismissed and this may result in the
+  // requestMediaKeySystemAccess request succeeding. However, the blink
+  // objects may have been cleared, so check if this is the case and simply
+  // reject the request.
+  blink::WebSecurityOrigin origin = request.getSecurityOrigin();
+  if (origin.isNull()) {
+    request.requestNotSupported("Unable to create MediaKeySystemAccess");
+    return;
+  }
+
   request.requestSucceeded(WebContentDecryptionModuleAccessImpl::Create(
-      request.keySystem(), request.getSecurityOrigin(),
-      accumulated_configuration, cdm_config, weak_factory_.GetWeakPtr()));
+      request.keySystem(), origin, accumulated_configuration, cdm_config,
+      weak_factory_.GetWeakPtr()));
 }
 
 void WebEncryptedMediaClientImpl::OnRequestNotSupported(
diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc
index 671c25e9..efe2fe45 100644
--- a/media/filters/gpu_video_decoder.cc
+++ b/media/filters/gpu_video_decoder.cc
@@ -5,6 +5,7 @@
 #include "media/filters/gpu_video_decoder.h"
 
 #include <algorithm>
+#include <array>
 #include <utility>
 
 #include "base/bind.h"
@@ -31,7 +32,44 @@
 #include "media/renderers/gpu_video_accelerator_factories.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
+#if defined(USE_PROPRIETARY_CODECS)
+#include "media/formats/mp4/box_definitions.h"
+#endif
+
 namespace media {
+namespace {
+
+// Size of shared-memory segments we allocate.  Since we reuse them we let them
+// be on the beefy side.
+static const size_t kSharedMemorySegmentBytes = 100 << 10;
+
+#if defined(OS_ANDROID) && defined(USE_PROPRIETARY_CODECS)
+// Extract the SPS and PPS lists from |extra_data|. Each SPS and PPS is prefixed
+// with 0x0001, the Annex B framing bytes. The out parameters are not modified
+// on failure.
+void ExtractSpsAndPps(const std::vector<uint8_t>& extra_data,
+                      std::vector<uint8_t>* sps_out,
+                      std::vector<uint8_t>* pps_out) {
+  mp4::AVCDecoderConfigurationRecord record;
+  if (!record.Parse(extra_data.data(), extra_data.size())) {
+    DVLOG(1) << "Failed to extract the SPS and PPS from extra_data";
+    return;
+  }
+
+  constexpr std::array<uint8_t, 4> prefix = {{0, 0, 0, 1}};
+  for (const std::vector<uint8_t>& sps : record.sps_list) {
+    sps_out->insert(sps_out->end(), prefix.begin(), prefix.end());
+    sps_out->insert(sps_out->end(), sps.begin(), sps.end());
+  }
+
+  for (const std::vector<uint8_t>& pps : record.pps_list) {
+    pps_out->insert(pps_out->end(), prefix.begin(), prefix.end());
+    pps_out->insert(pps_out->end(), pps.begin(), pps.end());
+  }
+}
+#endif
+
+}  // namespace
 
 const char GpuVideoDecoder::kDecoderName[] = "GpuVideoDecoder";
 
@@ -40,10 +78,6 @@
 // resources.
 enum { kMaxInFlightDecodes = 4 };
 
-// Size of shared-memory segments we allocate.  Since we reuse them we let them
-// be on the beefy side.
-static const size_t kSharedMemorySegmentBytes = 100 << 10;
-
 GpuVideoDecoder::SHMBuffer::SHMBuffer(std::unique_ptr<base::SharedMemory> m,
                                       size_t s)
     : shm(std::move(m)), size(s) {}
@@ -299,6 +333,14 @@
   vda_config.surface_id = surface_id;
   vda_config.is_deferred_initialization_allowed = true;
   vda_config.initial_expected_coded_size = config_.coded_size();
+
+#if defined(OS_ANDROID) && defined(USE_PROPRIETARY_CODECS)
+  // We pass the SPS and PPS on Android because it lets us initialize
+  // MediaCodec more reliably (http://crbug.com/649185).
+  if (config_.codec() == kCodecH264)
+    ExtractSpsAndPps(config_.extra_data(), &vda_config.sps, &vda_config.pps);
+#endif
+
   if (!vda_->Initialize(vda_config, this)) {
     DVLOG(1) << "VDA::Initialize failed.";
     base::ResetAndReturn(&init_cb_).Run(false);
diff --git a/media/gpu/android_video_decode_accelerator.cc b/media/gpu/android_video_decode_accelerator.cc
index 55eb738..f7851da 100644
--- a/media/gpu/android_video_decode_accelerator.cc
+++ b/media/gpu/android_video_decode_accelerator.cc
@@ -380,10 +380,15 @@
       codec_config_->codec_ != kCodecHEVC &&
 #endif
       codec_config_->codec_ != kCodecH264) {
-    LOG(ERROR) << "Unsupported profile: " << config.profile;
+    DLOG(ERROR) << "Unsupported profile: " << config.profile;
     return false;
   }
 
+  if (codec_config_->codec_ == kCodecH264) {
+    codec_config_->csd0_ = config.sps;
+    codec_config_->csd1_ = config.pps;
+  }
+
   // Only use MediaCodec for VP8/9 if it's likely backed by hardware
   // or if the stream is encrypted.
   if (IsMediaCodecSoftwareDecodingForbidden() &&
@@ -397,7 +402,7 @@
 
   auto gles_decoder = get_gles2_decoder_cb_.Run();
   if (!gles_decoder) {
-    LOG(ERROR) << "Failed to get gles2 decoder instance.";
+    DLOG(ERROR) << "Failed to get gles2 decoder instance.";
     return false;
   }
 
@@ -1043,8 +1048,8 @@
   std::unique_ptr<VideoCodecBridge> codec(VideoCodecBridge::CreateDecoder(
       codec_config->codec_, codec_config->needs_protected_surface_,
       codec_config->initial_expected_coded_size_,
-      codec_config->surface_.j_surface().obj(), media_crypto, true,
-      require_software_codec));
+      codec_config->surface_.j_surface().obj(), media_crypto,
+      codec_config->csd0_, codec_config->csd1_, true, require_software_codec));
 
   return codec;
 }
diff --git a/media/gpu/android_video_decode_accelerator.h b/media/gpu/android_video_decode_accelerator.h
index 38b27e9..c8199a2 100644
--- a/media/gpu/android_video_decode_accelerator.h
+++ b/media/gpu/android_video_decode_accelerator.h
@@ -126,6 +126,10 @@
     // restrict the codec to be software only.
     AVDACodecAllocator::TaskType task_type_;
 
+    // Codec specific data (SPS and PPS for H264).
+    std::vector<uint8_t> csd0_;
+    std::vector<uint8_t> csd1_;
+
    protected:
     friend class base::RefCountedThreadSafe<CodecConfig>;
     virtual ~CodecConfig();
diff --git a/media/gpu/ipc/common/media_param_traits_macros.h b/media/gpu/ipc/common/media_param_traits_macros.h
index 563109c..e650f05 100644
--- a/media/gpu/ipc/common/media_param_traits_macros.h
+++ b/media/gpu/ipc/common/media_param_traits_macros.h
@@ -27,6 +27,8 @@
   IPC_STRUCT_TRAITS_MEMBER(surface_id)
   IPC_STRUCT_TRAITS_MEMBER(initial_expected_coded_size)
   IPC_STRUCT_TRAITS_MEMBER(supported_output_formats)
+  IPC_STRUCT_TRAITS_MEMBER(sps)
+  IPC_STRUCT_TRAITS_MEMBER(pps)
 IPC_STRUCT_TRAITS_END()
 
 IPC_STRUCT_TRAITS_BEGIN(media::CreateVideoEncoderParams)
diff --git a/media/gpu/media_foundation_video_encode_accelerator_win.cc b/media/gpu/media_foundation_video_encode_accelerator_win.cc
index 6aa3b17..dfd2710 100644
--- a/media/gpu/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/media_foundation_video_encode_accelerator_win.cc
@@ -402,7 +402,7 @@
 }
 
 void MediaFoundationVideoEncodeAccelerator::EncodeTask(
-    const scoped_refptr<VideoFrame>& frame,
+    scoped_refptr<VideoFrame> frame,
     bool force_keyframe) {
   DVLOG(3) << __func__;
   DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread());
@@ -430,6 +430,10 @@
   input_sample_->SetSampleTime(frame->timestamp().InMicroseconds() *
                                kOneMicrosecondInMFSampleTimeUnits);
   input_sample_->SetSampleDuration(kOneSecondInMFSampleTimeUnits / frame_rate_);
+
+  // Release frame after input is copied.
+  frame = nullptr;
+
   HRESULT hr = encoder_->ProcessInput(0, input_sample_.get(), 0);
   // According to MSDN, if encoder returns MF_E_NOTACCEPTING, we need to try
   // processing the output. This error indicates that encoder does not accept
diff --git a/media/gpu/media_foundation_video_encode_accelerator_win.h b/media/gpu/media_foundation_video_encode_accelerator_win.h
index 4ad1085..3060abb 100644
--- a/media/gpu/media_foundation_video_encode_accelerator_win.h
+++ b/media/gpu/media_foundation_video_encode_accelerator_win.h
@@ -77,7 +77,7 @@
   void NotifyError(VideoEncodeAccelerator::Error error);
 
   // Encoding tasks to be run on |encoder_thread_|.
-  void EncodeTask(const scoped_refptr<VideoFrame>& frame, bool force_keyframe);
+  void EncodeTask(scoped_refptr<VideoFrame> frame, bool force_keyframe);
 
   // Checks for and copies encoded output on |encoder_thread_|.
   void ProcessOutput();
diff --git a/media/video/video_decode_accelerator.h b/media/video/video_decode_accelerator.h
index 7e3c940d..b818a055 100644
--- a/media/video/video_decode_accelerator.h
+++ b/media/video/video_decode_accelerator.h
@@ -156,6 +156,12 @@
     // The list of picture buffer formats that the client knows how to use. An
     // empty list means any format is supported.
     std::vector<VideoPixelFormat> supported_output_formats;
+
+    // The H264 SPS and PPS configuration data. Not all clients populate these
+    // fields, so they should be parsed from the bitstream instead, if required.
+    // Each SPS and PPS is prefixed with the Annex B framing bytes: 0, 0, 0, 1.
+    std::vector<uint8_t> sps;
+    std::vector<uint8_t> pps;
   };
 
   // Interface for collaborating with picture interface to provide memory for
diff --git a/mojo/public/js/validation_unittests.js b/mojo/public/js/validation_unittests.js
index 4bdaa8f..0eb0e4dc 100644
--- a/mojo/public/js/validation_unittests.js
+++ b/mojo/public/js/validation_unittests.js
@@ -38,7 +38,7 @@
 
     TestMessageParserFailure.prototype.toString = function() {
       return 'Error: ' + this.message + ' for "' + this.input + '"';
-    }
+    };
 
     function checkData(data, expectedData, input) {
       if (data.byteLength != expectedData.byteLength) {
@@ -225,8 +225,6 @@
     for (var i = 0; i < testFiles.length; i++) {
       // TODO(hansmuller) Temporarily skipping array pointer overflow tests
       // because JS numbers are limited to 53 bits.
-      // TODO(yzshen) Skipping struct versioning tests (tests with "mthd11"
-      // in the name) because the feature is not supported in JS yet.
       // TODO(rudominer): Temporarily skipping 'no-such-method',
       // 'invalid_request_flags', and 'invalid_response_flags' until additional
       // logic in *RequestValidator and *ResponseValidator is ported from
@@ -234,7 +232,6 @@
       // TODO(crbug/640298): Implement max recursion depth for JS.
       // TODO(crbug/628104): Support struct map keys for JS.
       if (testFiles[i].indexOf("overflow") != -1 ||
-          testFiles[i].indexOf("mthd11") != -1 ||
           testFiles[i].indexOf("conformance_mthd19") != -1 ||
           testFiles[i].indexOf("conformance_mthd20") != -1 ||
           testFiles[i].indexOf("no_such_method") != -1 ||
@@ -302,7 +299,7 @@
       var validationError = noError;
       testConnection.router_.validationErrorHandler = function(err) {
         validationError = err;
-      }
+      };
 
       testConnection.router_.connector_.waitForNextMessage();
       checkValidationResult(testFiles[i], validationError);
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
index 48e36c61..a3f4f00 100644
--- a/mojo/public/js/validator.js
+++ b/mojo/public/js/validator.js
@@ -81,7 +81,7 @@
       return false;
 
     return true;
-  }
+  };
 
   Validator.prototype.claimRange = function(start, numBytes) {
     if (this.isValidRange(start, numBytes)) {
@@ -89,7 +89,7 @@
       return true;
     }
     return false;
-  }
+  };
 
   Validator.prototype.claimHandle = function(index) {
     if (index === codec.kEncodedInvalidHandleValue)
@@ -101,7 +101,7 @@
     // This is safe because handle indices are uint32.
     this.handleIndex = index + 1;
     return true;
-  }
+  };
 
   Validator.prototype.validateEnum = function(offset, enumClass, nullable) {
     // Note: Assumes that enums are always 32 bits! But this matches
@@ -120,14 +120,13 @@
     if (!this.claimHandle(index))
       return validationError.ILLEGAL_HANDLE;
     return validationError.NONE;
-  }
+  };
 
   Validator.prototype.validateInterface = function(offset, nullable) {
     return this.validateHandle(offset, nullable);
-  }
+  };
 
-  Validator.prototype.validateStructHeader =
-      function(offset, minNumBytes, minVersion) {
+  Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
     if (!codec.isAligned(offset))
       return validationError.MISALIGNED_OBJECT;
 
@@ -135,20 +134,44 @@
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     var numBytes = this.message.buffer.getUint32(offset);
-    var version = this.message.buffer.getUint32(offset + 4);
 
-    // Backward compatibility is not yet supported.
-    if (numBytes < minNumBytes || version < minVersion)
+    if (numBytes < minNumBytes)
       return validationError.UNEXPECTED_STRUCT_HEADER;
 
     if (!this.claimRange(offset, numBytes))
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     return validationError.NONE;
-  }
+  };
+
+  Validator.prototype.validateStructVersion = function(offset, versionSizes) {
+    var numBytes = this.message.buffer.getUint32(offset);
+    var version = this.message.buffer.getUint32(offset + 4);
+
+    if (version <= versionSizes[versionSizes.length - 1].version) {
+      // Scan in reverse order to optimize for more recent versionSizes.
+      for (var i = versionSizes.length - 1; i >= 0; --i) {
+        if (version >= versionSizes[i].version) {
+          if (numBytes == versionSizes[i].numBytes)
+            break;
+          return validationError.UNEXPECTED_STRUCT_HEADER;
+        }
+      }
+    } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
+      return validationError.UNEXPECTED_STRUCT_HEADER;
+    }
+
+    return validationError.NONE;
+  };
+
+  Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
+    var structVersion = this.message.buffer.getUint32(offset + 4);
+    return fieldVersion <= structVersion;
+  };
 
   Validator.prototype.validateMessageHeader = function() {
-    var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 0);
+
+    var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
     if (err != validationError.NONE)
       return err;
 
@@ -174,7 +197,7 @@
       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
 
     return validationError.NONE;
-  }
+  };
 
   // Returns the message.buffer relative offset this pointer "points to",
   // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
@@ -185,7 +208,7 @@
       return NULL_MOJO_POINTER;
     var bufferOffset = offset + pointerValue;
     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
-  }
+  };
 
   Validator.prototype.decodeUnionSize = function(offset) {
     return this.message.buffer.getUint32(offset);
@@ -208,7 +231,7 @@
 
     return this.validateArray(arrayOffset, elementSize, elementType,
                               expectedDimensionSizes, currentDimension);
-  }
+  };
 
   Validator.prototype.validateStructPointer = function(
       offset, structClass, nullable) {
@@ -221,7 +244,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
 
     return structClass.validate(this, structOffset);
-  }
+  };
 
   Validator.prototype.validateUnion = function(
       offset, unionClass, nullable) {
@@ -232,7 +255,7 @@
     }
 
     return unionClass.validate(this, offset);
-  }
+  };
 
   Validator.prototype.validateNestedUnion = function(
       offset, unionClass, nullable) {
@@ -245,7 +268,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
 
     return this.validateUnion(unionOffset, unionClass, nullable);
-  }
+  };
 
   // This method assumes that the array at arrayPointerOffset has
   // been validated.
@@ -253,7 +276,7 @@
   Validator.prototype.arrayLength = function(arrayPointerOffset) {
     var arrayOffset = this.decodePointer(arrayPointerOffset);
     return this.message.buffer.getUint32(arrayOffset + 4);
-  }
+  };
 
   Validator.prototype.validateMapPointer = function(
       offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
@@ -268,7 +291,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
 
     var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
-    var err = this.validateStructHeader(structOffset, mapEncodedSize, 0);
+    var err = this.validateStructHeader(structOffset, mapEncodedSize);
     if (err !== validationError.NONE)
         return err;
 
@@ -301,12 +324,12 @@
       return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
 
     return validationError.NONE;
-  }
+  };
 
   Validator.prototype.validateStringPointer = function(offset, nullable) {
     return this.validateArrayPointer(
         offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
-  }
+  };
 
   // Similar to Array_Data<T>::Validate()
   // mojo/public/cpp/bindings/lib/array_internal.h
@@ -363,7 +386,7 @@
       return this.validateEnum(elementsOffset, elementType.cls, nullable);
 
     return validationError.NONE;
-  }
+  };
 
   // Note: the |offset + i * elementSize| computation in the validateFooElements
   // methods below is "safe" because elementSize <= 8, offset and
@@ -379,7 +402,7 @@
         return err;
     }
     return validationError.NONE;
-  }
+  };
 
   Validator.prototype.validateInterfaceElements =
       function(offset, numElements, nullable) {
@@ -391,7 +414,7 @@
         return err;
     }
     return validationError.NONE;
-  }
+  };
 
   // The elementClass parameter is the element type of the element arrays.
   Validator.prototype.validateArrayElements =
@@ -407,7 +430,7 @@
         return err;
     }
     return validationError.NONE;
-  }
+  };
 
   Validator.prototype.validateStructElements =
       function(offset, numElements, structClass, nullable) {
@@ -420,7 +443,7 @@
         return err;
     }
     return validationError.NONE;
-  }
+  };
 
   var exports = {};
   exports.validationError = validationError;
diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
index b16d673d..25e532b 100644
--- a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
@@ -34,16 +34,40 @@
 
   {{struct.name}}.validate = function(messageValidator, offset) {
     var err;
-    err = messageValidator.validateStructHeader(offset, {{struct.name}}.encodedSize, {{struct.versions[-1].version}});
+    err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
     if (err !== validator.validationError.NONE)
         return err;
 
+    var kVersionSizes = [
+{%- for version in struct.versions %}
+      {version: {{version.version}}, numBytes: {{version.num_bytes}}}{% if not loop.last %},
+      {%- endif -%}
+{%  endfor %}
+    ];
+    err = messageValidator.validateStructVersion(offset, kVersionSizes);
+    if (err !== validator.validationError.NONE)
+        return err;
+
+{#- Before validating fields introduced at a certain version, we need to add
+    a version check, which makes sure we skip further validation if |object|
+    is from an earlier version. |last_checked_version| records the last
+    version that we have added such version check. #}
 {%- from "validation_macros.tmpl" import validate_struct_field %}
-{%- for packed_field in struct.packed.packed_fields %}
+{%- set last_checked_version = 0 %}
+{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %}
 {%-   set offset = packed_field|field_offset %}
 {%-   set field = packed_field.field %}
 {%-   set name = struct.name ~ '.' ~ field.name %}
+{%    if field|is_object_field or field|is_any_handle_or_interface_field or
+          field|is_enum_field %}
+{%      if packed_field.min_version > last_checked_version %}
+{%        set last_checked_version = packed_field.min_version %}
+    // version check {{name}}
+    if (!messageValidator.isFieldInStructVersion({{packed_field.min_version}}))
+      return validator.validationError.NONE;
+{%-     endif -%}
 {{validate_struct_field(field, offset, name)|indent(4)}}
+{%-   endif %}
 {%- endfor %}
 
     return validator.validationError.NONE;
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index 7924e8a..3900e50 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -354,33 +354,44 @@
 def IsBoolField(field):
   return mojom.IsBoolKind(field.kind)
 
+def IsObjectField(field):
+  return mojom.IsObjectKind(field.kind)
+
+def IsAnyHandleOrInterfaceField(field):
+  return mojom.IsAnyHandleOrInterfaceKind(field.kind)
+
+def IsEnumField(field):
+  return mojom.IsEnumKind(field.kind)
+
 
 class Generator(generator.Generator):
 
   js_filters = {
-    "default_value": JavaScriptDefaultValue,
-    "payload_size": JavaScriptPayloadSize,
     "decode_snippet": JavaScriptDecodeSnippet,
+    "default_value": JavaScriptDefaultValue,
     "encode_snippet": JavaScriptEncodeSnippet,
-    "union_decode_snippet": JavaScriptUnionDecodeSnippet,
-    "union_encode_snippet": JavaScriptUnionEncodeSnippet,
     "expression_to_text": ExpressionToText,
     "field_offset": JavaScriptFieldOffset,
     "has_callbacks": mojom.HasCallbacks,
+    "is_any_handle_or_interface_field": IsAnyHandleOrInterfaceField,
     "is_array_pointer_field": IsArrayPointerField,
     "is_bool_field": IsBoolField,
     "is_enum_field": IsEnumField,
-    "is_map_pointer_field": IsMapPointerField,
-    "is_struct_pointer_field": IsStructPointerField,
-    "is_string_pointer_field": IsStringPointerField,
-    "is_union_field": IsUnionField,
     "is_handle_field": IsHandleField,
     "is_interface_field": IsInterfaceField,
     "is_interface_request_field": IsInterfaceRequestField,
-    "js_type": JavaScriptType,
+    "is_map_pointer_field": IsMapPointerField,
+    "is_object_field": IsObjectField,
+    "is_string_pointer_field": IsStringPointerField,
+    "is_struct_pointer_field": IsStructPointerField,
+    "is_union_field": IsUnionField,
     "js_proxy_method_parameter_value": JavaScriptProxyMethodParameterValue,
     "js_stub_method_parameter_value": JavaScriptStubMethodParameterValue,
+    "js_type": JavaScriptType,
+    "payload_size": JavaScriptPayloadSize,
     "stylize_method": generator.StudlyCapsToCamel,
+    "union_decode_snippet": JavaScriptUnionDecodeSnippet,
+    "union_encode_snippet": JavaScriptUnionEncodeSnippet,
     "validate_array_params": JavaScriptValidateArrayParams,
     "validate_enum_params": JavaScriptValidateEnumParams,
     "validate_handle_params": JavaScriptValidateHandleParams,
diff --git a/net/base/file_stream_context.cc b/net/base/file_stream_context.cc
index ac94008..f059be7 100644
--- a/net/base/file_stream_context.cc
+++ b/net/base/file_stream_context.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/debug/alias.h"
 #include "base/files/file_path.h"
 #include "base/location.h"
 #include "base/profiler/scoped_tracker.h"
@@ -83,9 +84,7 @@
 void FileStream::Context::Open(const base::FilePath& path,
                                int open_flags,
                                const CompletionCallback& callback) {
-  // TODO(jkarlin): Change back to a DCHECK once https://crbug.com/487732 is
-  // fixed.
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   bool posted = base::PostTaskAndReplyWithResult(
       task_runner_.get(),
@@ -95,13 +94,12 @@
       base::Bind(&Context::OnOpenCompleted, base::Unretained(this), callback));
   DCHECK(posted);
 
+  last_operation_ = OPEN;
   async_in_progress_ = true;
 }
 
 void FileStream::Context::Close(const CompletionCallback& callback) {
-  // TODO(jkarlin): Change back to a DCHECK once https://crbug.com/487732 is
-  // fixed.
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
   bool posted = base::PostTaskAndReplyWithResult(
       task_runner_.get(),
       FROM_HERE,
@@ -111,14 +109,13 @@
                  IntToInt64(callback)));
   DCHECK(posted);
 
+  last_operation_ = CLOSE;
   async_in_progress_ = true;
 }
 
 void FileStream::Context::Seek(int64_t offset,
                                const Int64CompletionCallback& callback) {
-  // TODO(jkarlin): Change back to a DCHECK once https://crbug.com/487732 is
-  // fixed.
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   bool posted = base::PostTaskAndReplyWithResult(
       task_runner_.get(), FROM_HERE,
@@ -126,13 +123,12 @@
       base::Bind(&Context::OnAsyncCompleted, base::Unretained(this), callback));
   DCHECK(posted);
 
+  last_operation_ = SEEK;
   async_in_progress_ = true;
 }
 
 void FileStream::Context::Flush(const CompletionCallback& callback) {
-  // TODO(jkarlin): Change back to a DCHECK once https://crbug.com/487732 is
-  // fixed.
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   bool posted = base::PostTaskAndReplyWithResult(
       task_runner_.get(),
@@ -143,6 +139,7 @@
                  IntToInt64(callback)));
   DCHECK(posted);
 
+  last_operation_ = FLUSH;
   async_in_progress_ = true;
 }
 
@@ -150,6 +147,16 @@
   return file_.IsValid();
 }
 
+void FileStream::Context::CheckNoAsyncInProgress() const {
+  if (!async_in_progress_)
+    return;
+  LastOperation state = last_operation_;
+  base::debug::Alias(&state);
+  // TODO(xunjieli): Once https://crbug.com/487732 is fixed, use
+  // DCHECK(!async_in_progress_) directly at call places.
+  CHECK(!async_in_progress_);
+}
+
 FileStream::Context::OpenResult FileStream::Context::OpenFileImpl(
     const base::FilePath& path, int open_flags) {
 #if defined(OS_POSIX)
@@ -208,13 +215,12 @@
   // TODO(ananta)
   // Replace this CHECK with a DCHECK once we figure out the root cause of
   // http://crbug.com/455066
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   if (file_.IsValid()) {
     bool posted = task_runner_.get()->PostTask(
-        FROM_HERE,
-        base::Bind(base::IgnoreResult(&Context::CloseFileImpl),
-                   base::Owned(this)));
+        FROM_HERE, base::Bind(base::IgnoreResult(&Context::CloseFileImpl),
+                              base::Owned(this)));
     DCHECK(posted);
   } else {
     delete this;
@@ -237,6 +243,7 @@
   // should be reset before Close() because it shouldn't run if any async
   // operation is in progress.
   async_in_progress_ = false;
+  last_operation_ = NONE;
   if (orphaned_)
     CloseAndDelete();
   else
diff --git a/net/base/file_stream_context.h b/net/base/file_stream_context.h
index aaff5adb..6b44580 100644
--- a/net/base/file_stream_context.h
+++ b/net/base/file_stream_context.h
@@ -125,6 +125,27 @@
     DISALLOW_COPY_AND_ASSIGN(OpenResult);
   };
 
+  // TODO(xunjieli): Remove after crbug.com/487732 is fixed.
+  enum LastOperation {
+    // FileStream has a pending Open().
+    OPEN,
+    // FileStream has a pending Write().
+    WRITE,
+    // FileStream has a pending Read().
+    READ,
+    // FileStream has a pending Seek().
+    SEEK,
+    // FileStream has a pending Flush().
+    FLUSH,
+    // FileStream has a pending Close().
+    CLOSE,
+    // FileStream doesn't have any pending operation.
+    NONE
+  };
+
+  // TODO(xunjieli): Remove after crbug.com/487732 is fixed.
+  void CheckNoAsyncInProgress() const;
+
   ////////////////////////////////////////////////////////////////////////////
   // Platform-independent methods implemented in file_stream_context.cc.
   ////////////////////////////////////////////////////////////////////////////
@@ -217,6 +238,10 @@
 
   base::File file_;
   bool async_in_progress_;
+
+  // TODO(xunjieli): Remove after crbug.com/487732 is fixed.
+  LastOperation last_operation_;
+
   bool orphaned_;
   scoped_refptr<base::TaskRunner> task_runner_;
 
diff --git a/net/base/file_stream_context_posix.cc b/net/base/file_stream_context_posix.cc
index af98c782..67c772a0 100644
--- a/net/base/file_stream_context_posix.cc
+++ b/net/base/file_stream_context_posix.cc
@@ -32,6 +32,7 @@
                              const scoped_refptr<base::TaskRunner>& task_runner)
     : file_(std::move(file)),
       async_in_progress_(false),
+      last_operation_(NONE),
       orphaned_(false),
       task_runner_(task_runner) {}
 
@@ -41,7 +42,7 @@
 int FileStream::Context::Read(IOBuffer* in_buf,
                               int buf_len,
                               const CompletionCallback& callback) {
-  DCHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   scoped_refptr<IOBuffer> buf = in_buf;
   const bool posted = base::PostTaskAndReplyWithResult(
@@ -54,13 +55,14 @@
   DCHECK(posted);
 
   async_in_progress_ = true;
+  last_operation_ = READ;
   return ERR_IO_PENDING;
 }
 
 int FileStream::Context::Write(IOBuffer* in_buf,
                                int buf_len,
                                const CompletionCallback& callback) {
-  DCHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
   scoped_refptr<IOBuffer> buf = in_buf;
   const bool posted = base::PostTaskAndReplyWithResult(
@@ -73,6 +75,7 @@
   DCHECK(posted);
 
   async_in_progress_ = true;
+  last_operation_ = WRITE;
   return ERR_IO_PENDING;
 }
 
diff --git a/net/base/file_stream_context_win.cc b/net/base/file_stream_context_win.cc
index 5028f1959..4e417dc 100644
--- a/net/base/file_stream_context_win.cc
+++ b/net/base/file_stream_context_win.cc
@@ -38,6 +38,7 @@
 
 FileStream::Context::Context(const scoped_refptr<base::TaskRunner>& task_runner)
     : async_in_progress_(false),
+      last_operation_(NONE),
       orphaned_(false),
       task_runner_(task_runner),
       async_read_initiated_(false),
@@ -49,6 +50,7 @@
                              const scoped_refptr<base::TaskRunner>& task_runner)
     : file_(std::move(file)),
       async_in_progress_(false),
+      last_operation_(NONE),
       orphaned_(false),
       task_runner_(task_runner),
       async_read_initiated_(false),
@@ -67,11 +69,13 @@
 int FileStream::Context::Read(IOBuffer* buf,
                               int buf_len,
                               const CompletionCallback& callback) {
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
+
   DCHECK(!async_read_initiated_);
   DCHECK(!async_read_completed_);
   DCHECK(!io_complete_for_read_received_);
 
+  last_operation_ = READ;
   IOCompletionIsPending(callback, buf);
 
   async_read_initiated_ = true;
@@ -88,8 +92,9 @@
 int FileStream::Context::Write(IOBuffer* buf,
                                int buf_len,
                                const CompletionCallback& callback) {
-  CHECK(!async_in_progress_);
+  CheckNoAsyncInProgress();
 
+  last_operation_ = WRITE;
   result_ = 0;
 
   DWORD bytes_written = 0;
@@ -137,8 +142,10 @@
   DCHECK(!callback_.is_null());
   DCHECK(async_in_progress_);
 
-  if (!async_read_initiated_)
+  if (!async_read_initiated_) {
+    last_operation_ = NONE;
     async_in_progress_ = false;
+  }
 
   if (orphaned_) {
     io_complete_for_read_received_ = true;
@@ -178,6 +185,7 @@
     async_read_initiated_ = false;
     io_complete_for_read_received_ = false;
     async_read_completed_ = false;
+    last_operation_ = NONE;
     async_in_progress_ = false;
   }
   CompletionCallback temp_callback = callback_;
@@ -188,6 +196,7 @@
 }
 
 void FileStream::Context::DeleteOrphanedContext() {
+  last_operation_ = NONE;
   async_in_progress_ = false;
   callback_.Reset();
   in_flight_buf_ = NULL;
diff --git a/net/http/http_request_headers.cc b/net/http/http_request_headers.cc
index 8e667fc4..70f6d27a 100644
--- a/net/http/http_request_headers.cc
+++ b/net/http/http_request_headers.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
+#include "net/base/escape.h"
 #include "net/http/http_log_util.h"
 #include "net/http/http_util.h"
 #include "net/log/net_log_capture_mode.h"
@@ -188,14 +189,16 @@
     const std::string* request_line,
     NetLogCaptureMode capture_mode) const {
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
-  dict->SetString("line", *request_line);
+  dict->SetString("line", EscapeNonASCII(*request_line));
   base::ListValue* headers = new base::ListValue();
-  for (HeaderVector::const_iterator it = headers_.begin();
-       it != headers_.end(); ++it) {
+  for (HeaderVector::const_iterator it = headers_.begin(); it != headers_.end();
+       ++it) {
     std::string log_value =
         ElideHeaderValueForNetLog(capture_mode, it->key, it->value);
-    headers->AppendString(
-        base::StringPrintf("%s: %s", it->key.c_str(), log_value.c_str()));
+    std::string escaped_name = EscapeNonASCII(it->key);
+    std::string escaped_value = EscapeNonASCII(log_value);
+    headers->AppendString(base::StringPrintf("%s: %s", escaped_name.c_str(),
+                                             escaped_value.c_str()));
   }
   dict->Set("headers", headers);
   return std::move(dict);
diff --git a/net/net.gypi b/net/net.gypi
index 3b28267..f4a5fdfc 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -212,6 +212,8 @@
       'socket/connection_attempts.h',
       'socket/next_proto.h',
       'socket/socket.h',
+      'socket/socket_bio_adapter.cc',
+      'socket/socket_bio_adapter.h',
       'socket/socket_performance_watcher.h',
       'socket/socket_performance_watcher_factory.h',
       'socket/ssl_client_socket.cc',
@@ -1831,6 +1833,7 @@
       'socket/mock_client_socket_pool_manager.cc',
       'socket/mock_client_socket_pool_manager.h',
       'socket/sequenced_socket_data_unittest.cc',
+      'socket/socket_bio_adapter_unittest.cc',
       'socket/socks5_client_socket_unittest.cc',
       'socket/socks_client_socket_pool_unittest.cc',
       'socket/socks_client_socket_unittest.cc',
diff --git a/net/nqe/observation_buffer.h b/net/nqe/observation_buffer.h
index 9a4ce3a..b9339a8 100644
--- a/net/nqe/observation_buffer.h
+++ b/net/nqe/observation_buffer.h
@@ -70,20 +70,18 @@
 
   // Returns true iff the |percentile| value of the observations in this
   // buffer is available. Sets |result| to the computed |percentile|
-  // value among all observations since |begin_timestamp|. If the value is
-  // unavailable, false is returned and |result| is not modified. Percentile
-  // value is unavailable if all the values in observation buffer are older
-  // than |begin_timestamp|.
+  // value of all observations made on or after |begin_timestamp|. If the
+  // value is unavailable, false is returned and |result| is not modified.
+  // Percentile value is unavailable if all the values in observation buffer are
+  // older than |begin_timestamp|.
   // |result| must not be null.
   // TODO(tbansal): Move out param |result| as the last param of the function.
-  bool GetPercentile(const base::TimeTicks& begin_timestamp,
+  bool GetPercentile(base::TimeTicks begin_timestamp,
                      ValueType* result,
                      int percentile,
                      const std::vector<NetworkQualityObservationSource>&
                          disallowed_observation_sources) const {
-    DCHECK(result);
-    DCHECK_GE(Capacity(), Size());
-    // Stores WeightedObservation in increasing order of value.
+    // Stores weighted observations in increasing order by value.
     std::vector<WeightedObservation<ValueType>> weighted_observations;
 
     // Total weight of all observations in |weighted_observations|.
@@ -94,13 +92,6 @@
     if (weighted_observations.empty())
       return false;
 
-    DCHECK(!weighted_observations.empty());
-    DCHECK_GT(total_weight, 0.0);
-
-    // |weighted_observations| may have a smaller size than observations_ since
-    // the former contains only the observations later than begin_timestamp.
-    DCHECK_GE(observations_.size(), weighted_observations.size());
-
     double desired_weight = percentile / 100.0 * total_weight;
 
     double cumulative_weight_seen_so_far = 0.0;
@@ -122,6 +113,72 @@
     return true;
   }
 
+  // Returns true iff the weighted average of the observations in this
+  // buffer is available. Sets |result| to the computed weighted average value
+  // of all observations made on or after |begin_timestamp|. If the value is
+  // unavailable, false is returned and |result| is not modified. The unweighted
+  // average value is unavailable if all the values in the observation buffer
+  // are older than |begin_timestamp|. |result| must not be null.
+  bool GetWeightedAverage(base::TimeTicks begin_timestamp,
+                          const std::vector<NetworkQualityObservationSource>&
+                              disallowed_observation_sources,
+                          ValueType* result) const {
+    // Stores weighted observations in increasing order by value.
+    std::vector<WeightedObservation<ValueType>> weighted_observations;
+
+    // Total weight of all observations in |weighted_observations|.
+    double total_weight = 0.0;
+
+    ComputeWeightedObservations(begin_timestamp, weighted_observations,
+                                &total_weight, disallowed_observation_sources);
+    if (weighted_observations.empty())
+      return false;
+
+    // Weighted average is the sum of observations times their respective
+    // weights, divided by the sum of the weights of all observations.
+    double total_weight_times_value = 0.0;
+    for (const auto& weighted_observation : weighted_observations) {
+      total_weight_times_value +=
+          (weighted_observation.weight *
+           ConvertValueTypeToDouble(weighted_observation.value));
+    }
+
+    ConvertDoubleToValueType(total_weight_times_value / total_weight, result);
+    return true;
+  }
+
+  // Returns true iff the unweighted average of the observations in this buffer
+  // is available. Sets |result| to the computed unweighted average value of
+  // all observations made on or after |begin_timestamp|. If the value is
+  // unavailable, false is returned and |result| is not modified. The weighted
+  // average value is unavailable if all the values in the observation buffer
+  // are older than |begin_timestamp|. |result| must not be null.
+  bool GetUnweightedAverage(base::TimeTicks begin_timestamp,
+                            const std::vector<NetworkQualityObservationSource>&
+                                disallowed_observation_sources,
+                            ValueType* result) const {
+    // Stores weighted observations in increasing order by value.
+    std::vector<WeightedObservation<ValueType>> weighted_observations;
+
+    // Total weight of all observations in |weighted_observations|.
+    double total_weight = 0.0;
+
+    ComputeWeightedObservations(begin_timestamp, weighted_observations,
+                                &total_weight, disallowed_observation_sources);
+    if (weighted_observations.empty())
+      return false;
+
+    // The unweighted average is the sum of all observations divided by the
+    // number of observations.
+    double total_value = 0.0;
+    for (const auto& weighted_observation : weighted_observations)
+      total_value += ConvertValueTypeToDouble(weighted_observation.value);
+
+    ConvertDoubleToValueType(total_value / weighted_observations.size(),
+                             result);
+    return true;
+  }
+
   void SetTickClockForTesting(std::unique_ptr<base::TickClock> tick_clock) {
     tick_clock_ = std::move(tick_clock);
   }
@@ -130,6 +187,21 @@
   // Maximum number of observations that can be held in the ObservationBuffer.
   static const size_t kMaximumObservationsBufferSize = 300;
 
+  // Convert different ValueTypes to double to make it possible to perform
+  // arithmetic operations on them.
+  double ConvertValueTypeToDouble(base::TimeDelta input) const {
+    return input.InMilliseconds();
+  }
+  double ConvertValueTypeToDouble(int32_t input) const { return input; }
+
+  // Convert double to different ValueTypes.
+  void ConvertDoubleToValueType(double input, base::TimeDelta* output) const {
+    *output = base::TimeDelta::FromMilliseconds(input);
+  }
+  void ConvertDoubleToValueType(double input, int32_t* output) const {
+    *output = input;
+  }
+
   // Computes the weighted observations and stores them in
   // |weighted_observations| sorted by ascending |WeightedObservation.value|.
   // Only the observations with timestamp later than |begin_timestamp| are
@@ -171,6 +243,14 @@
     // Sort the samples by value in ascending order.
     std::sort(weighted_observations.begin(), weighted_observations.end());
     *total_weight = total_weight_observations;
+
+    DCHECK_LE(0.0, *total_weight);
+    DCHECK(weighted_observations.empty() || 0.0 < *total_weight);
+
+    // |weighted_observations| may have a smaller size than |observations_|
+    // since the former contains only the observations later than
+    // |begin_timestamp|.
+    DCHECK_GE(observations_.size(), weighted_observations.size());
   }
 
   // Holds observations sorted by time, with the oldest observation at the
diff --git a/net/nqe/observation_buffer_unittest.cc b/net/nqe/observation_buffer_unittest.cc
index 4da54e44..501c074 100644
--- a/net/nqe/observation_buffer_unittest.cc
+++ b/net/nqe/observation_buffer_unittest.cc
@@ -185,7 +185,7 @@
   ObservationBuffer<int32_t> int_buffer(0.5);
   ObservationBuffer<base::TimeDelta> time_delta_buffer(0.5);
   const base::TimeTicks now = base::TimeTicks::Now();
-  const base::TimeTicks very_old = now - base::TimeDelta::FromDays(365);
+  const base::TimeTicks very_old = now - base::TimeDelta::FromDays(7);
 
   int32_t result;
   base::TimeDelta time_delta_result;
@@ -198,7 +198,7 @@
       base::TimeTicks(), &time_delta_result, 50,
       std::vector<NetworkQualityObservationSource>()));
 
-  // First 50 samples have very old timestamp.
+  // First 50 samples have very old timestamps.
   for (int i = 1; i <= 50; ++i) {
     int_buffer.AddObservation(Observation<int32_t>(
         i, very_old, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
@@ -207,7 +207,7 @@
         NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
   }
 
-  // Next 50 (i.e., from 51 to 100) have recent timestamp.
+  // Next 50 (i.e., from 51 to 100) have recent timestamps.
   for (int i = 51; i <= 100; ++i) {
     int_buffer.AddObservation(Observation<int32_t>(
         i, now, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
@@ -224,12 +224,11 @@
     // what is expected due to floating point computation errors and integer
     // rounding off errors.
     EXPECT_TRUE(int_buffer.GetPercentile(
-        base::TimeTicks(), &result, i,
-        std::vector<NetworkQualityObservationSource>()));
+        very_old, &result, i, std::vector<NetworkQualityObservationSource>()));
     EXPECT_NEAR(result, 51 + 0.49 * i, 1);
 
     EXPECT_TRUE(time_delta_buffer.GetPercentile(
-        base::TimeTicks(), &time_delta_result, i,
+        very_old, &time_delta_result, i,
         std::vector<NetworkQualityObservationSource>()));
     EXPECT_NEAR(time_delta_result.InMilliseconds(), 51 + 0.49 * i, 1);
   }
@@ -242,6 +241,122 @@
       std::vector<NetworkQualityObservationSource>()));
 }
 
+#if !defined(OS_WIN)
+// Disabled on OS_WIN since the GetUnweightedAverage() and
+// GetUnweightedAverage() functions are not yet called outside tests, and so the
+// compiler on Windows does not generate the object code for these functions.
+// TODO(tbansal): crbug.com/656170 Enable these tests on Windows once these
+// functions are called outside the tests.
+TEST(NetworkQualityObservationBufferTest,
+     UnweightedAverageDifferentTimestamps) {
+  ObservationBuffer<base::TimeDelta> time_delta_buffer(0.5);
+  ObservationBuffer<int32_t> int_buffer(0.5);
+  const base::TimeTicks now = base::TimeTicks::Now();
+  const base::TimeTicks very_old = now - base::TimeDelta::FromDays(7);
+
+  base::TimeDelta time_delta_result;
+  int32_t int_result;
+
+  // Network quality should be unavailable when no observations are available.
+  EXPECT_FALSE(time_delta_buffer.GetUnweightedAverage(
+      base::TimeTicks(), std::vector<NetworkQualityObservationSource>(),
+      &time_delta_result));
+  EXPECT_FALSE(int_buffer.GetUnweightedAverage(
+      base::TimeTicks(), std::vector<NetworkQualityObservationSource>(),
+      &int_result));
+
+  // The first 50 samples have very old timestamps.
+  for (int i = 1; i <= 50; ++i) {
+    time_delta_buffer.AddObservation(Observation<base::TimeDelta>(
+        base::TimeDelta::FromMilliseconds(i), very_old,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+    int_buffer.AddObservation(Observation<int32_t>(
+        i, very_old, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+  }
+
+  // The next 50 (i.e., from 51 to 100) samples have recent timestamps.
+  for (int i = 51; i <= 100; ++i) {
+    time_delta_buffer.AddObservation(Observation<base::TimeDelta>(
+        base::TimeDelta::FromMilliseconds(i), now,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+    int_buffer.AddObservation(Observation<int32_t>(
+        i, now, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+  }
+
+  // All samples have equal weight. So, the unweighted average is the average of
+  // all samples.
+  EXPECT_TRUE(time_delta_buffer.GetUnweightedAverage(
+      very_old, std::vector<NetworkQualityObservationSource>(),
+      &time_delta_result));
+  EXPECT_NEAR(time_delta_result.InMilliseconds(), (1 + 100) / 2, 1);
+
+  EXPECT_TRUE(int_buffer.GetUnweightedAverage(
+      very_old, std::vector<NetworkQualityObservationSource>(), &int_result));
+  EXPECT_NEAR(int_result, (1 + 100) / 2, 1);
+
+  EXPECT_FALSE(time_delta_buffer.GetUnweightedAverage(
+      now + base::TimeDelta::FromSeconds(1),
+      std::vector<NetworkQualityObservationSource>(), &time_delta_result));
+  EXPECT_FALSE(int_buffer.GetUnweightedAverage(
+      now + base::TimeDelta::FromSeconds(1),
+      std::vector<NetworkQualityObservationSource>(), &int_result));
+}
+
+TEST(NetworkQualityObservationBufferTest, WeightedAverageDifferentTimestamps) {
+  ObservationBuffer<base::TimeDelta> time_delta_buffer(0.5);
+  ObservationBuffer<int32_t> int_buffer(0.5);
+  const base::TimeTicks now = base::TimeTicks::Now();
+  const base::TimeTicks very_old = now - base::TimeDelta::FromDays(7);
+
+  base::TimeDelta time_delta_result;
+  int32_t int_result;
+
+  // Network quality should be unavailable when no observations are available.
+  EXPECT_FALSE(time_delta_buffer.GetWeightedAverage(
+      base::TimeTicks(), std::vector<NetworkQualityObservationSource>(),
+      &time_delta_result));
+  EXPECT_FALSE(int_buffer.GetWeightedAverage(
+      base::TimeTicks(), std::vector<NetworkQualityObservationSource>(),
+      &int_result));
+
+  // The first 50 samples have very old timestamps.
+  for (int i = 1; i <= 50; ++i) {
+    time_delta_buffer.AddObservation(Observation<base::TimeDelta>(
+        base::TimeDelta::FromMilliseconds(i), very_old,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+    int_buffer.AddObservation(Observation<int32_t>(
+        i, very_old, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+  }
+
+  // The next 50 (i.e., from 51 to 100) samples have recent timestamps.
+  for (int i = 51; i <= 100; ++i) {
+    time_delta_buffer.AddObservation(Observation<base::TimeDelta>(
+        base::TimeDelta::FromMilliseconds(i), now,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+    int_buffer.AddObservation(Observation<int32_t>(
+        i, now, NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST));
+  }
+
+  // The older samples have very little weight, and so the weighted average must
+  // be approximately equal to the average of all recent samples.
+  EXPECT_TRUE(time_delta_buffer.GetWeightedAverage(
+      very_old, std::vector<NetworkQualityObservationSource>(),
+      &time_delta_result));
+  EXPECT_NEAR(time_delta_result.InMilliseconds(), (51 + 100) / 2, 1);
+
+  EXPECT_TRUE(int_buffer.GetWeightedAverage(
+      very_old, std::vector<NetworkQualityObservationSource>(), &int_result));
+  EXPECT_NEAR(int_result, (51 + 100) / 2, 1);
+
+  EXPECT_FALSE(time_delta_buffer.GetWeightedAverage(
+      now + base::TimeDelta::FromSeconds(1),
+      std::vector<NetworkQualityObservationSource>(), &time_delta_result));
+  EXPECT_FALSE(int_buffer.GetWeightedAverage(
+      now + base::TimeDelta::FromSeconds(1),
+      std::vector<NetworkQualityObservationSource>(), &int_result));
+}
+#endif  // !defined(OS_WIN)
+
 // Verifies that the percentiles are correctly computed when some of the
 // observation sources are disallowed. All observations have the same timestamp.
 TEST(NetworkQualityObservationBufferTest, DisallowedObservationSources) {
diff --git a/net/quic/core/crypto/crypto_handshake_message.cc b/net/quic/core/crypto/crypto_handshake_message.cc
index 9fcf6ff0..530d2f80 100644
--- a/net/quic/core/crypto/crypto_handshake_message.cc
+++ b/net/quic/core/crypto/crypto_handshake_message.cc
@@ -227,6 +227,9 @@
       case kMSPC:
       case kSRBF:
       case kSWND:
+      case kMIDS:
+      case kSCLS:
+      case kTCID:
         // uint32_t value
         if (it->second.size() == 4) {
           uint32_t value;
diff --git a/net/quic/core/quic_flags_list.h b/net/quic/core/quic_flags_list.h
index 15e1b7a..ba7f31c 100644
--- a/net/quic/core/quic_flags_list.h
+++ b/net/quic/core/quic_flags_list.h
@@ -162,3 +162,7 @@
 // If true, enable bugfix for FHOL experiment (fin-only
 // WritevStreamData).
 QUIC_FLAG(bool, FLAGS_quic_bugfix_fhol_writev_fin_only_v2, true)
+
+// If true, v33 QUIC client uses 1 bit to specify 8-byte connection id in
+// public flag.
+QUIC_FLAG(bool, FLAGS_quic_remove_v33_hacks2, false)
diff --git a/net/quic/core/quic_framer.cc b/net/quic/core/quic_framer.cc
index 6111e8d1..c94749f9 100644
--- a/net/quic/core/quic_framer.cc
+++ b/net/quic/core/quic_framer.cc
@@ -724,6 +724,11 @@
     case PACKET_8BYTE_CONNECTION_ID:
       if (quic_version_ > QUIC_VERSION_32) {
         public_flags |= PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID;
+        if (!FLAGS_quic_remove_v33_hacks2 &&
+            perspective_ == Perspective::IS_CLIENT) {
+          public_flags |= PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID_OLD;
+        }
+
       } else {
         public_flags |= PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID_OLD;
       }
diff --git a/net/quic/core/quic_framer_test.cc b/net/quic/core/quic_framer_test.cc
index cd43369..1ae77ee 100644
--- a/net/quic/core/quic_framer_test.cc
+++ b/net/quic/core/quic_framer_test.cc
@@ -4232,7 +4232,8 @@
   unsigned char packet[] = {
       // public flags (version, 8 byte connection_id)
       static_cast<unsigned char>(
-          framer_.version() > QUIC_VERSION_32 ? 0x39 : 0x3D),
+          (FLAGS_quic_remove_v33_hacks2 &&
+            framer_.version() > QUIC_VERSION_32) ? 0x39 : 0x3D),
       // connection_id
       0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
       // version tag
@@ -4253,7 +4254,8 @@
   };
   unsigned char packet_34[] = {
       // public flags (version, 8 byte connection_id)
-      0x39,
+      static_cast<unsigned char>(
+          FLAGS_quic_remove_v33_hacks2 ? 0x39 : 0x3D),
       // connection_id
       0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
       // version tag
@@ -4383,7 +4385,8 @@
   unsigned char packet[] = {
     // public flags (8 byte connection_id)
     static_cast<unsigned char>(
-        framer_.version() > QUIC_VERSION_32 ? 0x79 : 0x7D),
+        (FLAGS_quic_remove_v33_hacks2 &&
+         framer_.version() > QUIC_VERSION_32) ? 0x79 : 0x7D),
     // connection_id
     0x10, 0x32, 0x54, 0x76,
     0x98, 0xBA, 0xDC, 0xFE,
@@ -4411,7 +4414,8 @@
   };
   unsigned char packet_34[] = {
     // public flags (8 byte connection_id)
-    0x79,
+    static_cast<unsigned char>(
+        FLAGS_quic_remove_v33_hacks2 ? 0x79 : 0x7D),
     // connection_id
     0x10, 0x32, 0x54, 0x76,
     0x98, 0xBA, 0xDC, 0xFE,
diff --git a/net/socket/socket_bio_adapter.cc b/net/socket/socket_bio_adapter.cc
new file mode 100644
index 0000000..7f769c1
--- /dev/null
+++ b/net/socket/socket_bio_adapter.cc
@@ -0,0 +1,340 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/socket/socket_bio_adapter.h"
+
+#include <openssl/bio.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/openssl_ssl_util.h"
+
+namespace net {
+
+SocketBIOAdapter::SocketBIOAdapter(StreamSocket* socket,
+                                   int read_buffer_capacity,
+                                   int write_buffer_capacity,
+                                   Delegate* delegate)
+    : socket_(socket),
+      read_buffer_capacity_(read_buffer_capacity),
+      read_offset_(0),
+      read_result_(0),
+      write_buffer_capacity_(write_buffer_capacity),
+      write_buffer_used_(0),
+      write_error_(OK),
+      delegate_(delegate),
+      weak_factory_(this) {
+  bio_.reset(BIO_new(&kBIOMethod));
+  bio_->ptr = this;
+  bio_->init = 1;
+
+  read_callback_ = base::Bind(&SocketBIOAdapter::OnSocketReadComplete,
+                              weak_factory_.GetWeakPtr());
+  write_callback_ = base::Bind(&SocketBIOAdapter::OnSocketWriteComplete,
+                               weak_factory_.GetWeakPtr());
+}
+
+SocketBIOAdapter::~SocketBIOAdapter() {
+  // BIOs are reference-counted and may outlive the adapter. Clear the pointer
+  // so future operations fail.
+  bio_->ptr = nullptr;
+}
+
+bool SocketBIOAdapter::HasPendingReadData() {
+  return read_result_ > 0;
+}
+
+int SocketBIOAdapter::BIORead(char* out, int len) {
+  if (len <= 0)
+    return len;
+
+  // If there is no result available synchronously, report any Write() errors
+  // that were observed. Otherwise the application may have encountered a socket
+  // error while writing that would otherwise not be reported until the
+  // application attempted to write again - which it may never do. See
+  // https://crbug.com/249848.
+  if (write_error_ != OK && write_error_ != ERR_IO_PENDING &&
+      (read_result_ == 0 || read_result_ == ERR_IO_PENDING)) {
+    OpenSSLPutNetError(FROM_HERE, write_error_);
+    return -1;
+  }
+
+  if (read_result_ == 0) {
+    // Instantiate the read buffer and read from the socket. Although only |len|
+    // bytes were requested, intentionally read to the full buffer size. The SSL
+    // layer reads the record header and body in separate reads to avoid
+    // overreading, but issuing one is more efficient. SSL sockets are not
+    // reused after shutdown for non-SSL traffic, so overreading is fine.
+    DCHECK(!read_buffer_);
+    DCHECK_EQ(0, read_offset_);
+    read_buffer_ = new IOBuffer(read_buffer_capacity_);
+    int result = socket_->Read(read_buffer_.get(), read_buffer_capacity_,
+                               read_callback_);
+    if (result == ERR_IO_PENDING) {
+      read_result_ = ERR_IO_PENDING;
+    } else {
+      HandleSocketReadResult(result);
+    }
+  }
+
+  // There is a pending Read(). Inform the caller to retry when it completes.
+  if (read_result_ == ERR_IO_PENDING) {
+    BIO_set_retry_read(bio());
+    return -1;
+  }
+
+  // If the last Read() failed, report the error.
+  if (read_result_ < 0) {
+    OpenSSLPutNetError(FROM_HERE, read_result_);
+    return -1;
+  }
+
+  // Report the result of the last Read() if non-empty.
+  CHECK_LT(read_offset_, read_result_);
+  len = std::min(len, read_result_ - read_offset_);
+  memcpy(out, read_buffer_->data() + read_offset_, len);
+  read_offset_ += len;
+
+  // Release the buffer when empty.
+  if (read_offset_ == read_result_) {
+    read_buffer_ = nullptr;
+    read_offset_ = 0;
+    read_result_ = 0;
+  }
+
+  return len;
+}
+
+void SocketBIOAdapter::HandleSocketReadResult(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+
+  // If an EOF, canonicalize to ERR_CONNECTION_CLOSED here, so that higher
+  // levels don't report success.
+  if (result == 0)
+    result = ERR_CONNECTION_CLOSED;
+
+  read_result_ = result;
+
+  // The read buffer is no longer needed.
+  if (read_result_ <= 0)
+    read_buffer_ = nullptr;
+}
+
+void SocketBIOAdapter::OnSocketReadComplete(int result) {
+  DCHECK_EQ(ERR_IO_PENDING, read_result_);
+
+  HandleSocketReadResult(result);
+  delegate_->OnReadReady();
+}
+
+int SocketBIOAdapter::BIOWrite(const char* in, int len) {
+  if (len <= 0)
+    return len;
+
+  // If the write buffer is not empty, there must be a pending Write() to flush
+  // it.
+  DCHECK(write_buffer_used_ == 0 || write_error_ == ERR_IO_PENDING);
+
+  // If a previous Write() failed, report the error.
+  if (write_error_ != OK && write_error_ != ERR_IO_PENDING) {
+    OpenSSLPutNetError(FROM_HERE, write_error_);
+    return -1;
+  }
+
+  // Instantiate the write buffer if needed.
+  if (!write_buffer_) {
+    DCHECK_EQ(0, write_buffer_used_);
+    write_buffer_ = new GrowableIOBuffer;
+    write_buffer_->SetCapacity(write_buffer_capacity_);
+  }
+
+  // If the ring buffer is full, inform the caller to try again later.
+  if (write_buffer_used_ == write_buffer_->capacity()) {
+    BIO_set_retry_write(bio());
+    return -1;
+  }
+
+  int bytes_copied = 0;
+
+  // If there is space after the offset, fill it.
+  if (write_buffer_used_ < write_buffer_->RemainingCapacity()) {
+    int chunk =
+        std::min(write_buffer_->RemainingCapacity() - write_buffer_used_, len);
+    memcpy(write_buffer_->data() + write_buffer_used_, in, chunk);
+    in += chunk;
+    len -= chunk;
+    bytes_copied += chunk;
+    write_buffer_used_ += chunk;
+  }
+
+  // If there is still space for remaining data, try to wrap around.
+  if (len > 0 && write_buffer_used_ < write_buffer_->capacity()) {
+    // If there were any room after the offset, the previous branch would have
+    // filled it.
+    CHECK_LE(write_buffer_->RemainingCapacity(), write_buffer_used_);
+    int write_offset = write_buffer_used_ - write_buffer_->RemainingCapacity();
+    int chunk = std::min(len, write_buffer_->capacity() - write_buffer_used_);
+    memcpy(write_buffer_->StartOfBuffer() + write_offset, in, chunk);
+    in += chunk;
+    len -= chunk;
+    bytes_copied += chunk;
+    write_buffer_used_ += chunk;
+  }
+
+  // Either the buffer is now full or there is no more input.
+  DCHECK(len == 0 || write_buffer_used_ == write_buffer_->capacity());
+
+  // Schedule a socket Write() if necessary. (The ring buffer may previously
+  // have been empty.)
+  SocketWrite();
+
+  // If a read-interrupting write error was synchronously discovered,
+  // asynchronously notify OnReadReady. See https://crbug.com/249848. Avoid
+  // reentrancy by deferring it to a later event loop iteration.
+  if (write_error_ != OK && write_error_ != ERR_IO_PENDING &&
+      read_result_ == ERR_IO_PENDING) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(&SocketBIOAdapter::CallOnReadReady,
+                              weak_factory_.GetWeakPtr()));
+  }
+
+  return bytes_copied;
+}
+
+void SocketBIOAdapter::SocketWrite() {
+  while (write_error_ == OK && write_buffer_used_ > 0) {
+    int write_size =
+        std::min(write_buffer_used_, write_buffer_->RemainingCapacity());
+    int result =
+        socket_->Write(write_buffer_.get(), write_size, write_callback_);
+    if (result == ERR_IO_PENDING) {
+      write_error_ = ERR_IO_PENDING;
+      return;
+    }
+
+    HandleSocketWriteResult(result);
+  }
+}
+
+void SocketBIOAdapter::HandleSocketWriteResult(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+
+  if (result < 0) {
+    write_error_ = result;
+
+    // The write buffer is no longer needed.
+    write_buffer_ = nullptr;
+    write_buffer_used_ = 0;
+    return;
+  }
+
+  // Advance the ring buffer.
+  write_buffer_->set_offset(write_buffer_->offset() + result);
+  write_buffer_used_ -= result;
+  if (write_buffer_->RemainingCapacity() == 0)
+    write_buffer_->set_offset(0);
+  write_error_ = OK;
+
+  // Release the write buffer if empty.
+  if (write_buffer_used_ == 0)
+    write_buffer_ = nullptr;
+}
+
+void SocketBIOAdapter::OnSocketWriteComplete(int result) {
+  DCHECK_EQ(ERR_IO_PENDING, write_error_);
+
+  bool was_full = write_buffer_used_ == write_buffer_->capacity();
+
+  HandleSocketWriteResult(result);
+  SocketWrite();
+
+  // If transitioning from being unable to accept data to being able to, signal
+  // OnWriteReady.
+  if (was_full) {
+    base::WeakPtr<SocketBIOAdapter> guard(weak_factory_.GetWeakPtr());
+    delegate_->OnWriteReady();
+    // OnWriteReady may delete the adapter.
+    if (!guard)
+      return;
+  }
+
+  // Write errors are fed back into BIO_read once the read buffer is empty. If
+  // BIO_read is currently blocked, signal early that a read result is ready.
+  if (result < 0 && read_result_ == ERR_IO_PENDING)
+    delegate_->OnReadReady();
+}
+
+void SocketBIOAdapter::CallOnReadReady() {
+  if (read_result_ == ERR_IO_PENDING)
+    delegate_->OnReadReady();
+}
+
+SocketBIOAdapter* SocketBIOAdapter::GetAdapter(BIO* bio) {
+  DCHECK_EQ(&kBIOMethod, bio->method);
+  SocketBIOAdapter* adapter = reinterpret_cast<SocketBIOAdapter*>(bio->ptr);
+  if (adapter)
+    DCHECK_EQ(bio, adapter->bio());
+  return adapter;
+}
+
+int SocketBIOAdapter::BIOWriteWrapper(BIO* bio, const char* in, int len) {
+  BIO_clear_retry_flags(bio);
+
+  SocketBIOAdapter* adapter = GetAdapter(bio);
+  if (!adapter) {
+    OpenSSLPutNetError(FROM_HERE, ERR_UNEXPECTED);
+    return -1;
+  }
+
+  return adapter->BIOWrite(in, len);
+}
+
+int SocketBIOAdapter::BIOReadWrapper(BIO* bio, char* out, int len) {
+  BIO_clear_retry_flags(bio);
+
+  SocketBIOAdapter* adapter = GetAdapter(bio);
+  if (!adapter) {
+    OpenSSLPutNetError(FROM_HERE, ERR_UNEXPECTED);
+    return -1;
+  }
+
+  return adapter->BIORead(out, len);
+}
+
+long SocketBIOAdapter::BIOCtrlWrapper(BIO* bio,
+                                      int cmd,
+                                      long larg,
+                                      void* parg) {
+  switch (cmd) {
+    case BIO_CTRL_FLUSH:
+      // The SSL stack requires BIOs handle BIO_flush.
+      return 1;
+  }
+
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+const BIO_METHOD SocketBIOAdapter::kBIOMethod = {
+    0,        // type (unused)
+    nullptr,  // name (unused)
+    SocketBIOAdapter::BIOWriteWrapper,
+    SocketBIOAdapter::BIOReadWrapper,
+    nullptr,  // puts
+    nullptr,  // gets
+    SocketBIOAdapter::BIOCtrlWrapper,
+    nullptr,  // create
+    nullptr,  // destroy
+    nullptr,  // callback_ctrl
+};
+
+}  // namespace net
diff --git a/net/socket/socket_bio_adapter.h b/net/socket/socket_bio_adapter.h
new file mode 100644
index 0000000..d4c8172
--- /dev/null
+++ b/net/socket/socket_bio_adapter.h
@@ -0,0 +1,143 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_SOCKET_SOCKET_BIO_ADAPTER_H_
+#define NET_SOCKET_SOCKET_BIO_ADAPTER_H_
+
+#include <openssl/base.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class GrowableIOBuffer;
+class IOBuffer;
+class StreamSocket;
+
+// An adapter to convert between StreamSocket and OpenSSL BIO I/O models.
+//
+// BIO exposes a UNIX-like interface where BIO_read and BIO_write may either
+// succeed synchronously or be retried (with no memory between calls).
+// StreamSocket exposes an asynchronous interface where an asynchronous
+// operation continues running and completes with a callback.
+//
+// For reading, SocketBIOAdapter maintains a buffer to pass to
+// StreamSocket::Read. Once that Read completes, BIO_read synchronously drains
+// the buffer and signals BIO_should_read once empty.
+//
+// For writing, SocketBIOAdapter maintains a ring buffer of data to be written
+// to the StreamSocket. BIO_write synchronously copies data into the buffer or
+// signals BIO_should_write if the buffer is full. The ring buffer is drained
+// asynchronously into the socket. Note this means write errors are reported at
+// a later BIO_write.
+//
+// To work around this delay, write errors are also surfaced out of
+// BIO_read. Otherwise a failure in the final BIO_write of an application may go
+// unnoticed. If this occurs, OnReadReady will be signaled as if it were a read
+// error. See https://crbug.com/249848.
+class NET_EXPORT_PRIVATE SocketBIOAdapter {
+ public:
+  // A delegate interface for when the sockets are ready. BIO assumes external
+  // knowledge of when to retry operations (such as a select() loop for UNIX),
+  // which is signaled out of StreamSocket's callbacks here.
+  //
+  // Callers should implement these methods and, when signaled, retry the
+  // BIO_read or BIO_write. This usually is done by retrying a higher-level
+  // operation, such as SSL_read or SSL_write.
+  //
+  // Callers may assume that OnReadReady and OnWriteReady will only be called
+  // from a PostTask or StreamSocket callback.
+  class Delegate {
+   public:
+    // Called when the BIO is ready to handle BIO_read, after having previously
+    // been blocked.
+    virtual void OnReadReady() = 0;
+
+    // Called when the BIO is ready to handle BIO_write, after having previously
+    // been blocked.
+    virtual void OnWriteReady() = 0;
+
+   protected:
+    virtual ~Delegate() {}
+  };
+
+  // Creates a new SocketBIOAdapter for the specified socket. |socket| and
+  // |delegate| must remain valid for the lifetime of the SocketBIOAdapter.
+  SocketBIOAdapter(StreamSocket* socket,
+                   int read_buffer_capacity,
+                   int write_buffer_capacity,
+                   Delegate* delegate);
+  ~SocketBIOAdapter();
+
+  BIO* bio() { return bio_.get(); }
+
+  // Returns true if any data has been read from the underlying StreamSocket,
+  // but not yet consumed by the BIO.
+  bool HasPendingReadData();
+
+ private:
+  int BIORead(char* out, int len);
+  void HandleSocketReadResult(int result);
+  void OnSocketReadComplete(int result);
+
+  int BIOWrite(const char* in, int len);
+  void SocketWrite();
+  void HandleSocketWriteResult(int result);
+  void OnSocketWriteComplete(int result);
+  void CallOnReadReady();
+
+  static SocketBIOAdapter* GetAdapter(BIO* bio);
+  static int BIOReadWrapper(BIO* bio, char* out, int len);
+  static int BIOWriteWrapper(BIO* bio, const char* in, int len);
+  static long BIOCtrlWrapper(BIO* bio, int cmd, long larg, void* parg);
+
+  static const BIO_METHOD kBIOMethod;
+
+  bssl::UniquePtr<BIO> bio_;
+
+  // The pointer is non-owning so this class may be used with both
+  // ClientSocketHandles and raw StreamSockets.
+  StreamSocket* socket_;
+
+  CompletionCallback read_callback_;
+  CompletionCallback write_callback_;
+
+  // The capacity of the read buffer.
+  int read_buffer_capacity_;
+  // A buffer containing data from the most recent socket Read(). The buffer is
+  // deallocated when unused.
+  scoped_refptr<IOBuffer> read_buffer_;
+  // The number of bytes of read_buffer_ consumed.
+  int read_offset_;
+  // The result of the most recent socket Read(). If ERR_IO_PENDING, there is a
+  // socket Read() in progress. If another error, Read() has failed. Otherwise,
+  // it is the number of bytes in the buffer (zero if empty).
+  int read_result_;
+
+  // The capacity of the write buffer.
+  int write_buffer_capacity_;
+  // A ring buffer of data to be written to the transport. The offset of the
+  // buffer is the start of the ring buffer and is advanced on successful
+  // Write(). The buffer is deallocated when unused.
+  scoped_refptr<GrowableIOBuffer> write_buffer_;
+  // The number of bytes of data in write_buffer_.
+  int write_buffer_used_;
+  // The most recent socket Write() error. If ERR_IO_PENDING, there is a socket
+  // Write() in progress. If OK, there is no socket Write() in progress and none
+  // have failed.
+  int write_error_;
+
+  Delegate* delegate_;
+
+  base::WeakPtrFactory<SocketBIOAdapter> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(SocketBIOAdapter);
+};
+
+}  // namespace net
+
+#endif  // NET_SOCKET_SOCKET_BIO_ADAPTER_H_
diff --git a/net/socket/socket_bio_adapter_unittest.cc b/net/socket/socket_bio_adapter_unittest.cc
new file mode 100644
index 0000000..455f8fb
--- /dev/null
+++ b/net/socket/socket_bio_adapter_unittest.cc
@@ -0,0 +1,617 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/socket/socket_bio_adapter.h"
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "crypto/openssl_util.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/log/net_log_source.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/openssl_ssl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class SocketBIOAdapterTest : public testing::Test,
+                             public SocketBIOAdapter::Delegate {
+ protected:
+  std::unique_ptr<StreamSocket> MakeTestSocket(SocketDataProvider* data) {
+    data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+    factory_.AddSocketDataProvider(data);
+    std::unique_ptr<StreamSocket> socket = factory_.CreateTransportClientSocket(
+        AddressList(), nullptr, nullptr, NetLogSource());
+    CHECK_EQ(OK, socket->Connect(net::CompletionCallback()));
+    return socket;
+  }
+
+  void set_reset_on_write_ready(
+      std::unique_ptr<SocketBIOAdapter>* reset_on_write_ready) {
+    reset_on_write_ready_ = reset_on_write_ready;
+  }
+
+  void ExpectReadError(BIO* bio,
+                       int error,
+                       const crypto::OpenSSLErrStackTracer& tracer) {
+    // BIO_read should fail.
+    char buf;
+    EXPECT_EQ(-1, BIO_read(bio, &buf, 1));
+    EXPECT_EQ(error, MapOpenSSLError(SSL_ERROR_SSL, tracer));
+    EXPECT_FALSE(BIO_should_read(bio));
+
+    // Repeating the operation should replay the error.
+    EXPECT_EQ(-1, BIO_read(bio, &buf, 1));
+    EXPECT_EQ(error, MapOpenSSLError(SSL_ERROR_SSL, tracer));
+    EXPECT_FALSE(BIO_should_read(bio));
+  }
+
+  void ExpectBlockingRead(BIO* bio, void* buf, int len) {
+    // BIO_read should return a retryable error.
+    EXPECT_EQ(-1, BIO_read(bio, buf, len));
+    EXPECT_TRUE(BIO_should_read(bio));
+    EXPECT_EQ(0u, ERR_peek_error());
+
+    // Repeating the operation has the same result.
+    EXPECT_EQ(-1, BIO_read(bio, buf, len));
+    EXPECT_TRUE(BIO_should_read(bio));
+    EXPECT_EQ(0u, ERR_peek_error());
+  }
+
+  void ExpectWriteError(BIO* bio,
+                        int error,
+                        const crypto::OpenSSLErrStackTracer& tracer) {
+    // BIO_write should fail.
+    char buf = '?';
+    EXPECT_EQ(-1, BIO_write(bio, &buf, 1));
+    EXPECT_EQ(error, MapOpenSSLError(SSL_ERROR_SSL, tracer));
+    EXPECT_FALSE(BIO_should_write(bio));
+
+    // Repeating the operation should replay the error.
+    EXPECT_EQ(-1, BIO_write(bio, &buf, 1));
+    EXPECT_EQ(error, MapOpenSSLError(SSL_ERROR_SSL, tracer));
+    EXPECT_FALSE(BIO_should_write(bio));
+  }
+
+  void ExpectBlockingWrite(BIO* bio, const void* buf, int len) {
+    // BIO_write should return a retryable error.
+    EXPECT_EQ(-1, BIO_write(bio, buf, len));
+    EXPECT_TRUE(BIO_should_write(bio));
+    EXPECT_EQ(0u, ERR_peek_error());
+
+    // Repeating the operation has the same result.
+    EXPECT_EQ(-1, BIO_write(bio, buf, len));
+    EXPECT_TRUE(BIO_should_write(bio));
+    EXPECT_EQ(0u, ERR_peek_error());
+  }
+
+  void WaitForReadReady() {
+    expect_read_ready_ = true;
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(expect_read_ready_);
+  }
+
+  void WaitForWriteReady(SequencedSocketData* to_resume) {
+    expect_write_ready_ = true;
+    if (to_resume) {
+      to_resume->Resume();
+    }
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(expect_write_ready_);
+  }
+
+  void WaitForBothReady() {
+    expect_read_ready_ = true;
+    expect_write_ready_ = true;
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(expect_read_ready_);
+    EXPECT_FALSE(expect_write_ready_);
+  }
+
+  // SocketBIOAdapter::Delegate implementation:
+  void OnReadReady() override {
+    EXPECT_TRUE(expect_read_ready_);
+    expect_read_ready_ = false;
+  }
+
+  void OnWriteReady() override {
+    EXPECT_TRUE(expect_write_ready_);
+    expect_write_ready_ = false;
+    if (reset_on_write_ready_)
+      reset_on_write_ready_->reset();
+  }
+
+ private:
+  bool expect_read_ready_ = false;
+  bool expect_write_ready_ = false;
+  MockClientSocketFactory factory_;
+  std::unique_ptr<SocketBIOAdapter>* reset_on_write_ready_ = nullptr;
+};
+
+// Test that data can be read synchronously.
+TEST_F(SocketBIOAdapterTest, ReadSync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, 0, "hello"), MockRead(SYNCHRONOUS, 1, "world"),
+      MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET, 2),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), nullptr, 0);
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+  BIO* bio = adapter->bio();
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // Read the data synchronously. Although the buffer has room for both,
+  // BIO_read only reports one socket-level Read.
+  char buf[10];
+  EXPECT_EQ(5, BIO_read(bio, buf, sizeof(buf)));
+  EXPECT_EQ(0, memcmp("hello", buf, 5));
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // Consume the next portion one byte at a time.
+  EXPECT_EQ(1, BIO_read(bio, buf, 1));
+  EXPECT_EQ('w', buf[0]);
+  EXPECT_TRUE(adapter->HasPendingReadData());
+
+  EXPECT_EQ(1, BIO_read(bio, buf, 1));
+  EXPECT_EQ('o', buf[0]);
+  EXPECT_TRUE(adapter->HasPendingReadData());
+
+  // The remainder may be consumed in a single BIO_read.
+  EXPECT_EQ(3, BIO_read(bio, buf, sizeof(buf)));
+  EXPECT_EQ(0, memcmp("rld", buf, 3));
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // The error is available synchoronously.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that data can be read asynchronously.
+TEST_F(SocketBIOAdapterTest, ReadAsync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(ASYNC, 0, "hello"), MockRead(ASYNC, 1, "world"),
+      MockRead(ASYNC, ERR_CONNECTION_RESET, 2),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), nullptr, 0);
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+  BIO* bio = adapter->bio();
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // Attempt to read data. It will fail but schedule a Read.
+  char buf[10];
+  ExpectBlockingRead(bio, buf, sizeof(buf));
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // After waiting, the data is available.
+  WaitForReadReady();
+  EXPECT_TRUE(adapter->HasPendingReadData());
+
+  // The first read is now available synchronously.
+  EXPECT_EQ(5, BIO_read(bio, buf, sizeof(buf)));
+  EXPECT_EQ(0, memcmp("hello", buf, 5));
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // The adapter does not schedule another Read until BIO_read is next called.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // This time, under-request the data. The adapter should still read the full
+  // amount.
+  ExpectBlockingRead(bio, buf, 1);
+  EXPECT_FALSE(adapter->HasPendingReadData());
+  WaitForReadReady();
+  EXPECT_TRUE(adapter->HasPendingReadData());
+
+  // The next read is now available synchronously.
+  EXPECT_EQ(5, BIO_read(bio, buf, sizeof(buf)));
+  EXPECT_EQ(0, memcmp("world", buf, 5));
+  EXPECT_FALSE(adapter->HasPendingReadData());
+
+  // The error is not yet available.
+  ExpectBlockingRead(bio, buf, sizeof(buf));
+  WaitForReadReady();
+
+  // The error is now available synchoronously.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that synchronous EOF is mapped to ERR_CONNECTION_CLOSED.
+TEST_F(SocketBIOAdapterTest, ReadEOFSync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, 0, 0),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), nullptr, 0);
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+
+  ExpectReadError(adapter->bio(), ERR_CONNECTION_CLOSED, tracer);
+}
+
+// Test that asynchronous EOF is mapped to ERR_CONNECTION_CLOSED.
+TEST_F(SocketBIOAdapterTest, ReadEOFAsync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(ASYNC, 0, 0),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), nullptr, 0);
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+
+  char buf;
+  ExpectBlockingRead(adapter->bio(), &buf, 1);
+  WaitForReadReady();
+  ExpectReadError(adapter->bio(), ERR_CONNECTION_CLOSED, tracer);
+}
+
+// Test that data can be written synchronously.
+TEST_F(SocketBIOAdapterTest, WriteSync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockWrite writes[] = {
+      MockWrite(SYNCHRONOUS, 0, "hello"),
+      MockWrite(SYNCHRONOUS, 1, "wor"),
+      MockWrite(SYNCHRONOUS, 2, "ld"),
+      MockWrite(SYNCHRONOUS, 3, "helloworld"),
+      MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 4),
+  };
+
+  SequencedSocketData data(nullptr, 0, writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 10, 10, this);
+  BIO* bio = adapter->bio();
+
+  // Test data entering and leaving the buffer synchronously. The second write
+  // takes multiple iterations (events 0 to 2).
+  EXPECT_EQ(5, BIO_write(bio, "hello", 5));
+  EXPECT_EQ(5, BIO_write(bio, "world", 5));
+
+  // If writing larger than the buffer size, only part of the data is written
+  // (event 3).
+  EXPECT_EQ(10, BIO_write(bio, "helloworldhelloworld", 20));
+
+  // Writing "aaaaa" fails (event 4), but there is a write buffer, so errors
+  // are delayed.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // However once the error is registered, subsequent writes fail.
+  ExpectWriteError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that data can be written asynchronously.
+TEST_F(SocketBIOAdapterTest, WriteAsync) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockWrite writes[] = {
+      MockWrite(ASYNC, 0, "aaa"),
+      MockWrite(ASYNC, ERR_IO_PENDING, 1),  // pause
+      MockWrite(ASYNC, 2, "aabbbbb"),
+      MockWrite(ASYNC, 3, "ccc"),
+      MockWrite(ASYNC, 4, "ddd"),
+      MockWrite(ASYNC, ERR_IO_PENDING, 5),  // pause
+      MockWrite(ASYNC, 6, "dd"),
+      MockWrite(SYNCHRONOUS, 7, "e"),
+      MockWrite(SYNCHRONOUS, 8, "e"),
+      MockWrite(ASYNC, 9, "e"),
+      MockWrite(ASYNC, 10, "ee"),
+      MockWrite(ASYNC, ERR_IO_PENDING, 11),  // pause
+      MockWrite(ASYNC, 12, "eff"),
+      MockWrite(ASYNC, 13, "ggggggg"),
+      MockWrite(ASYNC, ERR_CONNECTION_RESET, 14),
+  };
+
+  SequencedSocketData data(nullptr, 0, writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 10, 10, this);
+  BIO* bio = adapter->bio();
+
+  // Data which fits in the buffer is returned synchronously, even if not
+  // flushed synchronously.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+  EXPECT_EQ(5, BIO_write(bio, "bbbbb", 5));
+
+  // The buffer contains:
+  //
+  //   [aaaaabbbbb]
+  //    ^
+
+  // The buffer is full now, so the next write will block.
+  ExpectBlockingWrite(bio, "zzzzz", 5);
+
+  // Let the first socket write complete (event 0) and pause (event 1).
+  WaitForWriteReady(nullptr);
+  EXPECT_TRUE(data.IsPaused());
+
+  // The buffer contains:
+  //
+  //   [...aabbbbb]
+  //       ^
+
+  // The ring buffer now has 3 bytes of space with "aabbbbb" still to be
+  // written. Attempting to write 3 bytes means 3 succeed.
+  EXPECT_EQ(3, BIO_write(bio, "cccccccccc", 10));
+
+  // The buffer contains:
+  //
+  //   [cccaabbbbb]
+  //       ^
+
+  // Drain the buffer (events 2 and 3).
+  WaitForWriteReady(&data);
+
+  // The buffer is now empty.
+
+  // Now test something similar but arrange for a BIO_write (the 'e's below) to
+  // wrap around the buffer.  Write five bytes into the buffer, flush the first
+  // three (event 4), and pause (event 5). OnWriteReady is not signaled because
+  // the buffer was not full.
+  EXPECT_EQ(5, BIO_write(bio, "ddddd", 5));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(data.IsPaused());
+
+  // The buffer contains:
+  //
+  //   [...dd.....]
+  //       ^
+
+  // The adapter maintains a ring buffer, so 6 bytes fit.
+  EXPECT_EQ(6, BIO_write(bio, "eeeeee", 6));
+
+  // The buffer contains:
+  //
+  //   [e..ddeeeee]
+  //       ^
+
+  // The remaining space may be filled in.
+  EXPECT_EQ(2, BIO_write(bio, "ffffffffff", 10));
+
+  // The buffer contains:
+  //
+  //   [effddeeeee]
+  //       ^
+
+  // Drain to the end of the ring buffer, so it wraps around (events 6 to 10)
+  // and pause (event 11). Test that synchronous and asynchronous writes both
+  // drain. The start of the buffer has now wrapped around.
+  WaitForWriteReady(&data);
+  EXPECT_TRUE(data.IsPaused());
+
+  // The buffer contains:
+  //
+  //   [eff.......]
+  //    ^
+
+  // Test wrapping around works correctly and the buffer may be appended to.
+  EXPECT_EQ(7, BIO_write(bio, "gggggggggg", 10));
+
+  // The buffer contains:
+  //
+  //   [effggggggg]
+  //    ^
+
+  // The buffer is full now, so the next write will block.
+  ExpectBlockingWrite(bio, "zzzzz", 5);
+
+  // Drain the buffer to confirm the ring buffer's contents are as expected
+  // (events 12 and 13).
+  WaitForWriteReady(&data);
+
+  // Write again so the write error may be discovered.
+  EXPECT_EQ(5, BIO_write(bio, "hhhhh", 5));
+
+  // Release the write error (event 14). At this point future BIO_write calls
+  // fail. The buffer was not full, so OnWriteReady is not signalled.
+  base::RunLoop().RunUntilIdle();
+  ExpectWriteError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that a failed socket write is reported through BIO_read and prevents it
+// from scheduling a socket read. See https://crbug.com/249848.
+TEST_F(SocketBIOAdapterTest, WriteStopsRead) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockWrite writes[] = {
+      MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 0),
+  };
+
+  SequencedSocketData data(nullptr, 0, writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+  BIO* bio = adapter->bio();
+
+  // The write fails, but there is a write buffer, so errors are delayed.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // The write error is surfaced out of BIO_read. There are no MockReads, so
+  // this also tests that no socket reads are attempted.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that a synchronous failed socket write interrupts a blocked
+// BIO_read. See https://crbug.com/249848.
+TEST_F(SocketBIOAdapterTest, SyncWriteInterruptsRead) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0),
+  };
+
+  MockWrite writes[] = {
+      MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET, 1),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+  BIO* bio = adapter->bio();
+
+  // Attempt to read from the transport. It will block indefinitely.
+  char buf;
+  ExpectBlockingRead(adapter->bio(), &buf, 1);
+
+  // Schedule a socket write.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // The write error triggers OnReadReady.
+  WaitForReadReady();
+
+  // The write error is surfaced out of BIO_read.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that an asynchronous failed socket write interrupts a blocked
+// BIO_read. See https://crbug.com/249848.
+TEST_F(SocketBIOAdapterTest, AsyncWriteInterruptsRead) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0),
+  };
+
+  MockWrite writes[] = {
+      MockWrite(ASYNC, ERR_CONNECTION_RESET, 1),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+  BIO* bio = adapter->bio();
+
+  // Attempt to read from the transport. It will block indefinitely.
+  char buf;
+  ExpectBlockingRead(adapter->bio(), &buf, 1);
+
+  // Schedule a socket write.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // The write error is signaled asynchronously and interrupts BIO_read, so
+  // OnReadReady is signaled. The write buffer was not full, so OnWriteReady is
+  // not signaled.
+  WaitForReadReady();
+
+  // The write error is surfaced out of BIO_read.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that an asynchronous failed socket write interrupts a blocked BIO_read,
+// signaling both if the buffer was full. See https://crbug.com/249848.
+TEST_F(SocketBIOAdapterTest, AsyncWriteInterruptsBoth) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0),
+  };
+
+  MockWrite writes[] = {
+      MockWrite(ASYNC, ERR_CONNECTION_RESET, 1),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 5, 5, this);
+  BIO* bio = adapter->bio();
+
+  // Attempt to read from the transport. It will block indefinitely.
+  char buf;
+  ExpectBlockingRead(adapter->bio(), &buf, 1);
+
+  // Schedule a socket write.
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // The write error is signaled asynchronously and interrupts BIO_read, so
+  // OnReadReady is signaled. The write buffer was full, so both OnWriteReady is
+  // also signaled.
+  WaitForBothReady();
+
+  // The write error is surfaced out of BIO_read.
+  ExpectReadError(bio, ERR_CONNECTION_RESET, tracer);
+}
+
+// Test that SocketBIOAdapter handles OnWriteReady deleting itself when both
+// need to be signaled.
+TEST_F(SocketBIOAdapterTest, DeleteOnWriteReady) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  MockRead reads[] = {
+      MockRead(SYNCHRONOUS, ERR_IO_PENDING, 0),
+  };
+
+  MockWrite writes[] = {
+      MockWrite(ASYNC, ERR_CONNECTION_RESET, 1),
+  };
+
+  SequencedSocketData data(reads, arraysize(reads), writes, arraysize(writes));
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 5, 5, this);
+  BIO* bio = adapter->bio();
+
+  // Arrange for OnReadReady and OnWriteReady to both be signaled due to write
+  // error propagation (see the AsyncWriteInterruptsBoth test).
+  char buf;
+  ExpectBlockingRead(adapter->bio(), &buf, 1);
+  EXPECT_EQ(5, BIO_write(bio, "aaaaa", 5));
+
+  // Both OnWriteReady and OnReadReady would be signaled, but OnWriteReady
+  // deletes the adapter first.
+  set_reset_on_write_ready(&adapter);
+  WaitForWriteReady(nullptr);
+
+  EXPECT_FALSE(adapter);
+}
+
+// Test that using a BIO after the underlying adapter is destroyed fails
+// gracefully.
+TEST_F(SocketBIOAdapterTest, Detached) {
+  crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
+
+  SequencedSocketData data(nullptr, 0, nullptr, 0);
+  std::unique_ptr<StreamSocket> socket = MakeTestSocket(&data);
+  std::unique_ptr<SocketBIOAdapter> adapter =
+      base::MakeUnique<SocketBIOAdapter>(socket.get(), 100, 100, this);
+
+  // Retain an additional reference to the BIO.
+  bssl::UniquePtr<BIO> bio(adapter->bio());
+  BIO_up_ref(bio.get());
+
+  // Release the adapter.
+  adapter.reset();
+
+  ExpectReadError(bio.get(), ERR_UNEXPECTED, tracer);
+  ExpectWriteError(bio.get(), ERR_UNEXPECTED, tracer);
+}
+
+}  // namespace net
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index 15c26f8..ef21477 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -201,10 +201,16 @@
   return writes_[write_index_++];
 }
 
+void StaticSocketDataHelper::Reset() {
+  read_index_ = 0;
+  write_index_ = 0;
+}
+
 bool StaticSocketDataHelper::VerifyWriteData(const std::string& data) {
   CHECK(!AllWriteDataConsumed());
-  // Check that what the actual data matches the expectations.
-  const MockWrite& next_write = PeekWrite();
+  // Check that the actual data matches the expectations, skipping over any
+  // pause events.
+  const MockWrite& next_write = PeekRealWrite();
   if (!next_write.data)
     return true;
 
@@ -221,9 +227,14 @@
   return expected_data == actual_data;
 }
 
-void StaticSocketDataHelper::Reset() {
-  read_index_ = 0;
-  write_index_ = 0;
+const MockWrite& StaticSocketDataHelper::PeekRealWrite() const {
+  for (size_t i = write_index_; i < write_count_; i++) {
+    if (writes_[i].mode != ASYNC || writes_[i].result != ERR_IO_PENDING)
+      return writes_[i];
+  }
+
+  CHECK(false) << "No write data available.";
+  return writes_[0];  // Avoid warning about unreachable missing return.
 }
 
 StaticSocketDataProvider::StaticSocketDataProvider()
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
index 4cc20d6..b0bbd89 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -280,12 +280,12 @@
                          size_t writes_count);
   ~StaticSocketDataHelper();
 
-  // These functions get access to the next available read and write data,
-  // or null if there is no more data available.
+  // These functions get access to the next available read and write data. They
+  // CHECK fail if there is no data available.
   const MockRead& PeekRead() const;
   const MockWrite& PeekWrite() const;
 
-  // Returns the current read or write , and then advances to the next one.
+  // Returns the current read or write, and then advances to the next one.
   const MockRead& AdvanceRead();
   const MockWrite& AdvanceWrite();
 
@@ -306,6 +306,10 @@
   bool AllWriteDataConsumed() const { return write_index_ >= write_count_; }
 
  private:
+  // Returns the next available read or write that is not a pause event. CHECK
+  // fails if no data is available.
+  const MockWrite& PeekRealWrite() const;
+
   MockRead* reads_;
   size_t read_index_;
   size_t read_count_;
@@ -432,7 +436,7 @@
     IDLE,        // No async operation is in progress.
     PENDING,     // An async operation in waiting for another opteration to
                  // complete.
-    COMPLETING,  // A task has been posted to complet an async operation.
+    COMPLETING,  // A task has been posted to complete an async operation.
     PAUSED,      // IO is paused until Resume() is called.
   };
 
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index 69258e3..bb3df44 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -200,6 +200,22 @@
   return std::move(dict);
 }
 
+int GetBufferSize(const char* field_trial) {
+  // Get buffer sizes from field trials, if possible. If values not present,
+  // use default.  Also make sure values are in reasonable range.
+  int buffer_size = kDefaultOpenSSLBufferSize;
+#if !defined(OS_NACL)
+  int override_buffer_size;
+  if (base::StringToInt(base::FieldTrialList::FindFullName(field_trial),
+                        &override_buffer_size)) {
+    buffer_size = override_buffer_size;
+    buffer_size = std::max(buffer_size, 1000);
+    buffer_size = std::min(buffer_size, 2 * kDefaultOpenSSLBufferSize);
+  }
+#endif  // !defined(OS_NACL)
+  return buffer_size;
+}
+
 }  // namespace
 
 class SSLClientSocketImpl::SSLContext {
@@ -476,12 +492,8 @@
     const HostPortPair& host_and_port,
     const SSLConfig& ssl_config,
     const SSLClientSocketContext& context)
-    : transport_send_busy_(false),
-      transport_recv_busy_(false),
-      pending_read_error_(kNoPendingResult),
+    : pending_read_error_(kNoPendingResult),
       pending_read_ssl_error_(SSL_ERROR_NONE),
-      transport_read_error_(OK),
-      transport_write_error_(OK),
       server_cert_chain_(new PeerCertificateChain(NULL)),
       completed_connect_(false),
       was_ever_used_(false),
@@ -633,6 +645,7 @@
   cert_verifier_request_.reset();
   channel_id_request_.Cancel();
   weak_factory_.InvalidateWeakPtrs();
+  transport_adapter_.reset();
 
   // Release user callbacks.
   user_connect_callback_.Reset();
@@ -670,11 +683,11 @@
   // If there is data read from the network that has not yet been consumed, do
   // not treat the connection as idle.
   //
-  // Note that this does not check |BIO_pending|, whether there is ciphertext
-  // that has not yet been flushed to the network. |Write| returns early, so
-  // this can cause race conditions which cause a socket to not be treated
-  // reusable when it should be. See https://crbug.com/466147.
-  if (BIO_wpending(transport_bio_.get()) > 0)
+  // Note that this does not check whether there is ciphertext that has not yet
+  // been flushed to the network. |Write| returns early, so this can cause race
+  // conditions which cause a socket to not be treated reusable when it should
+  // be. See https://crbug.com/466147.
+  if (transport_adapter_->HasPendingReadData())
     return false;
 
   return transport_->socket()->IsConnectedAndIdle();
@@ -778,7 +791,7 @@
   user_read_buf_ = buf;
   user_read_buf_len_ = buf_len;
 
-  int rv = DoReadLoop();
+  int rv = DoPayloadRead();
 
   if (rv == ERR_IO_PENDING) {
     user_read_callback_ = callback;
@@ -798,7 +811,7 @@
   user_write_buf_ = buf;
   user_write_buf_len_ = buf_len;
 
-  int rv = DoWriteLoop();
+  int rv = DoPayloadWrite();
 
   if (rv == ERR_IO_PENDING) {
     user_write_callback_ = callback;
@@ -820,9 +833,20 @@
   return transport_->socket()->SetSendBufferSize(size);
 }
 
+void SSLClientSocketImpl::OnReadReady() {
+  // During a renegotiation, either Read or Write calls may be blocked on a
+  // transport read.
+  RetryAllOperations();
+}
+
+void SSLClientSocketImpl::OnWriteReady() {
+  // During a renegotiation, either Read or Write calls may be blocked on a
+  // transport read.
+  RetryAllOperations();
+}
+
 int SSLClientSocketImpl::Init() {
   DCHECK(!ssl_);
-  DCHECK(!transport_bio_);
 
 #if defined(USE_NSS_CERTS)
   if (ssl_config_.cert_io_enabled) {
@@ -855,55 +879,16 @@
   if (session)
     SSL_set_session(ssl_.get(), session.get());
 
-  // Get read and write buffer sizes from field trials, if possible. If values
-  // not present, use default.  Also make sure values are in reasonable range.
-  int send_buffer_size = kDefaultOpenSSLBufferSize;
-#if !defined(OS_NACL)
-  int override_send_buffer_size;
-  if (base::StringToInt(base::FieldTrialList::FindFullName("SSLBufferSizeSend"),
-                        &override_send_buffer_size)) {
-    send_buffer_size = override_send_buffer_size;
-    send_buffer_size = std::max(send_buffer_size, 1000);
-    send_buffer_size =
-        std::min(send_buffer_size, 2 * kDefaultOpenSSLBufferSize);
-  }
-#endif  // !defined(OS_NACL)
-  send_buffer_ = new GrowableIOBuffer();
-  send_buffer_->SetCapacity(send_buffer_size);
+  transport_adapter_.reset(new SocketBIOAdapter(
+      transport_->socket(), GetBufferSize("SSLBufferSizeRecv"),
+      GetBufferSize("SSLBufferSizeSend"), this));
+  BIO* transport_bio = transport_adapter_->bio();
 
-  int recv_buffer_size = kDefaultOpenSSLBufferSize;
-#if !defined(OS_NACL)
-  int override_recv_buffer_size;
-  if (base::StringToInt(base::FieldTrialList::FindFullName("SSLBufferSizeRecv"),
-                        &override_recv_buffer_size)) {
-    recv_buffer_size = override_recv_buffer_size;
-    recv_buffer_size = std::max(recv_buffer_size, 1000);
-    recv_buffer_size =
-        std::min(recv_buffer_size, 2 * kDefaultOpenSSLBufferSize);
-  }
-#endif  // !defined(OS_NACL)
-  recv_buffer_ = new GrowableIOBuffer();
-  recv_buffer_->SetCapacity(recv_buffer_size);
+  BIO_up_ref(transport_bio);  // SSL_set0_rbio takes ownership.
+  SSL_set0_rbio(ssl_.get(), transport_bio);
 
-  BIO* ssl_bio = NULL;
-
-  // SSLClientSocketImpl retains ownership of the BIO buffers.
-  BIO* transport_bio_raw;
-  if (!BIO_new_bio_pair_external_buf(
-          &ssl_bio, send_buffer_->capacity(),
-          reinterpret_cast<uint8_t*>(send_buffer_->data()), &transport_bio_raw,
-          recv_buffer_->capacity(),
-          reinterpret_cast<uint8_t*>(recv_buffer_->data())))
-    return ERR_UNEXPECTED;
-  transport_bio_.reset(transport_bio_raw);
-  DCHECK(ssl_bio);
-  DCHECK(transport_bio_);
-
-  // Install a callback on OpenSSL's end to plumb transport errors through.
-  BIO_set_callback(ssl_bio, &SSLClientSocketImpl::BIOCallback);
-  BIO_set_callback_arg(ssl_bio, reinterpret_cast<char*>(this));
-
-  SSL_set_bio(ssl_.get(), ssl_bio, ssl_bio);
+  BIO_up_ref(transport_bio);  // SSL_set0_wbio takes ownership.
+  SSL_set0_wbio(ssl_.get(), transport_bio);
 
   DCHECK_LT(SSL3_VERSION, ssl_config_.version_min);
   DCHECK_LT(SSL3_VERSION, ssl_config_.version_max);
@@ -1031,21 +1016,6 @@
   base::ResetAndReturn(&user_write_callback_).Run(rv);
 }
 
-bool SSLClientSocketImpl::DoTransportIO() {
-  bool network_moved = false;
-  int rv;
-  // Read and write as much data as possible. The loop is necessary because
-  // Write() may return synchronously.
-  do {
-    rv = BufferSend();
-    if (rv != ERR_IO_PENDING && rv != 0)
-      network_moved = true;
-  } while (rv > 0);
-  if (transport_read_error_ == OK && BufferRecv() != ERR_IO_PENDING)
-    network_moved = true;
-  return network_moved;
-}
-
 // TODO(cbentzel): Remove including "base/threading/thread_local.h" and
 // g_first_run_completed once crbug.com/424386 is fixed.
 base::LazyInstance<base::ThreadLocalBoolean>::Leaky g_first_run_completed =
@@ -1333,36 +1303,6 @@
   }
 }
 
-void SSLClientSocketImpl::OnSendComplete(int result) {
-  if (next_handshake_state_ == STATE_HANDSHAKE) {
-    // In handshake phase.
-    OnHandshakeIOComplete(result);
-    return;
-  }
-
-  // During a renegotiation, a Read call may also be blocked on a transport
-  // write, so retry both operations.
-  PumpReadWriteEvents();
-}
-
-void SSLClientSocketImpl::OnRecvComplete(int result) {
-  TRACE_EVENT0("net", "SSLClientSocketImpl::OnRecvComplete");
-  if (next_handshake_state_ == STATE_HANDSHAKE) {
-    // In handshake phase.
-    OnHandshakeIOComplete(result);
-    return;
-  }
-
-  // Network layer received some data, check if client requested to read
-  // decrypted data.
-  if (!user_read_buf_.get())
-    return;
-
-  int rv = DoReadLoop();
-  if (rv != ERR_IO_PENDING)
-    DoReadCallback(rv);
-}
-
 int SSLClientSocketImpl::DoHandshakeLoop(int last_io_result) {
   TRACE_EVENT0("net", "SSLClientSocketImpl::DoHandshakeLoop");
   int rv = last_io_result;
@@ -1401,40 +1341,10 @@
         NOTREACHED() << "unexpected state" << state;
         break;
     }
-
-    bool network_moved = DoTransportIO();
-    if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
-      // In general we exit the loop if rv is ERR_IO_PENDING.  In this
-      // special case we keep looping even if rv is ERR_IO_PENDING because
-      // the transport IO may allow DoHandshake to make progress.
-      rv = OK;  // This causes us to stay in the loop.
-    }
   } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
   return rv;
 }
 
-int SSLClientSocketImpl::DoReadLoop() {
-  bool network_moved;
-  int rv;
-  do {
-    rv = DoPayloadRead();
-    network_moved = DoTransportIO();
-  } while (rv == ERR_IO_PENDING && network_moved);
-
-  return rv;
-}
-
-int SSLClientSocketImpl::DoWriteLoop() {
-  bool network_moved;
-  int rv;
-  do {
-    rv = DoPayloadWrite();
-    network_moved = DoTransportIO();
-  } while (rv == ERR_IO_PENDING && network_moved);
-
-  return rv;
-}
-
 int SSLClientSocketImpl::DoPayloadRead() {
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
 
@@ -1511,9 +1421,7 @@
 
     // Do not treat insufficient data as an error to return in the next call to
     // DoPayloadRead() - instead, let the call fall through to check SSL_read()
-    // again. This is because DoTransportIO() may complete in between the next
-    // call to DoPayloadRead(), and thus it is important to check SSL_read() on
-    // subsequent invocations to see if a complete record may now be read.
+    // again. The transport may have data available by then.
     if (pending_read_error_ == ERR_IO_PENDING)
       pending_read_error_ = kNoPendingResult;
   } else {
@@ -1561,158 +1469,38 @@
   return net_error;
 }
 
-void SSLClientSocketImpl::PumpReadWriteEvents() {
+void SSLClientSocketImpl::RetryAllOperations() {
+  // SSL_do_handshake, SSL_read, and SSL_write may all be retried when blocked,
+  // so retry all operations for simplicity. (Otherwise, SSL_get_error for each
+  // operation may be remembered to retry only the blocked ones.)
+
+  if (next_handshake_state_ == STATE_HANDSHAKE) {
+    // In handshake phase. The parameter to OnHandshakeIOComplete is unused.
+    OnHandshakeIOComplete(OK);
+    return;
+  }
+
   int rv_read = ERR_IO_PENDING;
   int rv_write = ERR_IO_PENDING;
-  bool network_moved;
-  do {
-    if (user_read_buf_.get())
-      rv_read = DoPayloadRead();
-    if (user_write_buf_.get())
-      rv_write = DoPayloadWrite();
-    network_moved = DoTransportIO();
-  } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
-           (user_read_buf_.get() || user_write_buf_.get()) && network_moved);
+  if (user_read_buf_)
+    rv_read = DoPayloadRead();
+  if (user_write_buf_)
+    rv_write = DoPayloadWrite();
 
   // Performing the Read callback may cause |this| to be deleted. If this
   // happens, the Write callback should not be invoked. Guard against this by
   // holding a WeakPtr to |this| and ensuring it's still valid.
   base::WeakPtr<SSLClientSocketImpl> guard(weak_factory_.GetWeakPtr());
-  if (user_read_buf_.get() && rv_read != ERR_IO_PENDING)
+  if (rv_read != ERR_IO_PENDING)
     DoReadCallback(rv_read);
 
   if (!guard.get())
     return;
 
-  if (user_write_buf_.get() && rv_write != ERR_IO_PENDING)
+  if (rv_write != ERR_IO_PENDING)
     DoWriteCallback(rv_write);
 }
 
-int SSLClientSocketImpl::BufferSend(void) {
-  if (transport_send_busy_)
-    return ERR_IO_PENDING;
-
-  size_t buffer_read_offset;
-  uint8_t* read_buf;
-  size_t max_read;
-  int status = BIO_zero_copy_get_read_buf(transport_bio_.get(), &read_buf,
-                                          &buffer_read_offset, &max_read);
-  DCHECK_EQ(status, 1);  // Should never fail.
-  if (!max_read)
-    return 0;  // Nothing pending in the OpenSSL write BIO.
-  CHECK_EQ(read_buf, reinterpret_cast<uint8_t*>(send_buffer_->StartOfBuffer()));
-  CHECK_LT(buffer_read_offset, static_cast<size_t>(send_buffer_->capacity()));
-  send_buffer_->set_offset(buffer_read_offset);
-
-  int rv = transport_->socket()->Write(
-      send_buffer_.get(), max_read,
-      base::Bind(&SSLClientSocketImpl::BufferSendComplete,
-                 base::Unretained(this)));
-  if (rv == ERR_IO_PENDING) {
-    transport_send_busy_ = true;
-  } else {
-    TransportWriteComplete(rv);
-  }
-  return rv;
-}
-
-int SSLClientSocketImpl::BufferRecv(void) {
-  if (transport_recv_busy_)
-    return ERR_IO_PENDING;
-
-  // Determine how much was requested from |transport_bio_| that was not
-  // actually available.
-  size_t requested = BIO_ctrl_get_read_request(transport_bio_.get());
-  if (requested == 0) {
-    // This is not a perfect match of error codes, as no operation is
-    // actually pending. However, returning 0 would be interpreted as
-    // a possible sign of EOF, which is also an inappropriate match.
-    return ERR_IO_PENDING;
-  }
-
-  // Known Issue: While only reading |requested| data is the more correct
-  // implementation, it has the downside of resulting in frequent reads:
-  // One read for the SSL record header (~5 bytes) and one read for the SSL
-  // record body. Rather than issuing these reads to the underlying socket
-  // (and constantly allocating new IOBuffers), a single Read() request to
-  // fill |transport_bio_| is issued. As long as an SSL client socket cannot
-  // be gracefully shutdown (via SSL close alerts) and re-used for non-SSL
-  // traffic, this over-subscribed Read()ing will not cause issues.
-
-  size_t buffer_write_offset;
-  uint8_t* write_buf;
-  size_t max_write;
-  int status = BIO_zero_copy_get_write_buf(transport_bio_.get(), &write_buf,
-                                           &buffer_write_offset, &max_write);
-  DCHECK_EQ(status, 1);  // Should never fail.
-  if (!max_write)
-    return ERR_IO_PENDING;
-
-  CHECK_EQ(write_buf,
-           reinterpret_cast<uint8_t*>(recv_buffer_->StartOfBuffer()));
-  CHECK_LT(buffer_write_offset, static_cast<size_t>(recv_buffer_->capacity()));
-
-  recv_buffer_->set_offset(buffer_write_offset);
-  int rv = transport_->socket()->Read(
-      recv_buffer_.get(), max_write,
-      base::Bind(&SSLClientSocketImpl::BufferRecvComplete,
-                 base::Unretained(this)));
-  if (rv == ERR_IO_PENDING) {
-    transport_recv_busy_ = true;
-  } else {
-    rv = TransportReadComplete(rv);
-  }
-  return rv;
-}
-
-void SSLClientSocketImpl::BufferSendComplete(int result) {
-  TransportWriteComplete(result);
-  OnSendComplete(result);
-}
-
-void SSLClientSocketImpl::BufferRecvComplete(int result) {
-  result = TransportReadComplete(result);
-  OnRecvComplete(result);
-}
-
-void SSLClientSocketImpl::TransportWriteComplete(int result) {
-  DCHECK(ERR_IO_PENDING != result);
-  int bytes_written = 0;
-  if (result < 0) {
-    // Record the error. Save it to be reported in a future read or write on
-    // transport_bio_'s peer.
-    transport_write_error_ = result;
-  } else {
-    bytes_written = result;
-  }
-  DCHECK_GE(send_buffer_->RemainingCapacity(), bytes_written);
-  int ret =
-      BIO_zero_copy_get_read_buf_done(transport_bio_.get(), bytes_written);
-  DCHECK_EQ(1, ret);
-  transport_send_busy_ = false;
-}
-
-int SSLClientSocketImpl::TransportReadComplete(int result) {
-  DCHECK(ERR_IO_PENDING != result);
-  // If an EOF, canonicalize to ERR_CONNECTION_CLOSED here so MapOpenSSLError
-  // does not report success.
-  if (result == 0)
-    result = ERR_CONNECTION_CLOSED;
-  int bytes_read = 0;
-  if (result < 0) {
-    // Received an error. Save it to be reported in a future read on
-    // transport_bio_'s peer.
-    transport_read_error_ = result;
-  } else {
-    bytes_read = result;
-  }
-  DCHECK_GE(recv_buffer_->RemainingCapacity(), bytes_read);
-  int ret = BIO_zero_copy_get_write_buf_done(transport_bio_.get(), bytes_read);
-  DCHECK_EQ(1, ret);
-  transport_recv_busy_ = false;
-  return result;
-}
-
 int SSLClientSocketImpl::VerifyCT() {
   const uint8_t* sct_list_raw;
   size_t sct_list_len;
@@ -1912,53 +1700,6 @@
   return 1;
 }
 
-long SSLClientSocketImpl::MaybeReplayTransportError(BIO* bio,
-                                                    int cmd,
-                                                    const char* argp,
-                                                    int argi,
-                                                    long argl,
-                                                    long retvalue) {
-  if (cmd == (BIO_CB_READ | BIO_CB_RETURN) && retvalue <= 0) {
-    // If there is no more data in the buffer, report any pending errors that
-    // were observed. Note that both the readbuf and the writebuf are checked
-    // for errors, since the application may have encountered a socket error
-    // while writing that would otherwise not be reported until the application
-    // attempted to write again - which it may never do. See
-    // https://crbug.com/249848.
-    if (transport_read_error_ != OK) {
-      OpenSSLPutNetError(FROM_HERE, transport_read_error_);
-      return -1;
-    }
-    if (transport_write_error_ != OK) {
-      OpenSSLPutNetError(FROM_HERE, transport_write_error_);
-      return -1;
-    }
-  } else if (cmd == BIO_CB_WRITE) {
-    // Because of the write buffer, this reports a failure from the previous
-    // write payload. If the current payload fails to write, the error will be
-    // reported in a future write or read to |bio|.
-    if (transport_write_error_ != OK) {
-      OpenSSLPutNetError(FROM_HERE, transport_write_error_);
-      return -1;
-    }
-  }
-  return retvalue;
-}
-
-// static
-long SSLClientSocketImpl::BIOCallback(BIO* bio,
-                                      int cmd,
-                                      const char* argp,
-                                      int argi,
-                                      long argl,
-                                      long retvalue) {
-  SSLClientSocketImpl* socket =
-      reinterpret_cast<SSLClientSocketImpl*>(BIO_get_callback_arg(bio));
-  CHECK(socket);
-  return socket->MaybeReplayTransportError(bio, cmd, argp, argi, argl,
-                                           retvalue);
-}
-
 void SSLClientSocketImpl::MaybeCacheSession() {
   // Only cache the session once both a new session has been established and the
   // certificate has been verified. Due to False Start, these events may happen
@@ -2097,14 +1838,9 @@
   if (signature_result_ == OK)
     signature_ = signature;
 
-  if (next_handshake_state_ == STATE_HANDSHAKE) {
-    OnHandshakeIOComplete(signature_result_);
-    return;
-  }
-
   // During a renegotiation, either Read or Write calls may be blocked on an
   // asynchronous private key operation.
-  PumpReadWriteEvents();
+  RetryAllOperations();
 }
 
 int SSLClientSocketImpl::TokenBindingAdd(const uint8_t** out,
diff --git a/net/socket/ssl_client_socket_impl.h b/net/socket/ssl_client_socket_impl.h
index ff235ed..010a6f00 100644
--- a/net/socket/ssl_client_socket_impl.h
+++ b/net/socket/ssl_client_socket_impl.h
@@ -26,6 +26,7 @@
 #include "net/cert/ct_verify_result.h"
 #include "net/log/net_log_with_source.h"
 #include "net/socket/client_socket_handle.h"
+#include "net/socket/socket_bio_adapter.h"
 #include "net/socket/ssl_client_socket.h"
 #include "net/ssl/channel_id_service.h"
 #include "net/ssl/openssl_ssl_util.h"
@@ -45,6 +46,7 @@
 
 class CertVerifier;
 class CTVerifier;
+class SocketBIOAdapter;
 class SSLCertRequestInfo;
 class SSLInfo;
 
@@ -52,7 +54,8 @@
     base::MRUCache<std::pair<TokenBindingType, std::string>,
                    std::vector<uint8_t>>;
 
-class SSLClientSocketImpl : public SSLClientSocket {
+class SSLClientSocketImpl : public SSLClientSocket,
+                            public SocketBIOAdapter::Delegate {
  public:
   // Takes ownership of the transport_socket, which may already be connected.
   // The given hostname will be compared with the name(s) in the server's
@@ -121,6 +124,10 @@
   int SetReceiveBufferSize(int32_t size) override;
   int SetSendBufferSize(int32_t size) override;
 
+  // SocketBIOAdapter implementation:
+  void OnReadReady() override;
+  void OnWriteReady() override;
+
  private:
   class PeerCertificateChain;
   class SSLContext;
@@ -131,7 +138,6 @@
   void DoReadCallback(int result);
   void DoWriteCallback(int result);
 
-  bool DoTransportIO();
   int DoHandshake();
   int DoHandshakeComplete(int result);
   int DoChannelIDLookup();
@@ -142,26 +148,16 @@
   void UpdateServerCert();
 
   void OnHandshakeIOComplete(int result);
-  void OnSendComplete(int result);
-  void OnRecvComplete(int result);
 
   int DoHandshakeLoop(int last_io_result);
-  int DoReadLoop();
-  int DoWriteLoop();
   int DoPayloadRead();
   int DoPayloadWrite();
 
   // Called when an asynchronous event completes which may have blocked the
-  // pending Read or Write calls, if any. Retries both state machines and, if
-  // complete, runs the respective callbacks.
-  void PumpReadWriteEvents();
+  // pending Connect, Read or Write calls, if any. Retries all state machines
+  // and, if complete, runs the respective callbacks.
+  void RetryAllOperations();
 
-  int BufferSend();
-  int BufferRecv();
-  void BufferSendComplete(int result);
-  void BufferRecvComplete(int result);
-  void TransportWriteComplete(int result);
-  int TransportReadComplete(int result);
   int VerifyCT();
 
   // Callback from the SSL layer that indicates the remote server is requesting
@@ -173,25 +169,6 @@
   // certificates don't change during renegotiation.
   int CertVerifyCallback(X509_STORE_CTX* store_ctx);
 
-  // Called during an operation on |transport_bio_|'s peer. Checks saved
-  // transport error state and, if appropriate, returns an error through
-  // OpenSSL's error system.
-  long MaybeReplayTransportError(BIO* bio,
-                                 int cmd,
-                                 const char* argp,
-                                 int argi,
-                                 long argl,
-                                 long retvalue);
-
-  // Callback from the SSL layer when an operation is performed on
-  // |transport_bio_|'s peer.
-  static long BIOCallback(BIO* bio,
-                          int cmd,
-                          const char* argp,
-                          int argi,
-                          long argl,
-                          long retvalue);
-
   // Called after the initial handshake completes and after the server
   // certificate has been verified. The order of handshake completion and
   // certificate verification depends on whether the connection was false
@@ -258,14 +235,6 @@
                           const crypto::OpenSSLErrStackTracer& tracer,
                           OpenSSLErrorInfo* info);
 
-  bool transport_send_busy_;
-  bool transport_recv_busy_;
-
-  // Buffers which are shared by BoringSSL and SSLClientSocketImpl.
-  // GrowableIOBuffer is used to keep ownership and setting offset.
-  scoped_refptr<GrowableIOBuffer> send_buffer_;
-  scoped_refptr<GrowableIOBuffer> recv_buffer_;
-
   CompletionCallback user_connect_callback_;
   CompletionCallback user_read_callback_;
   CompletionCallback user_write_callback_;
@@ -293,15 +262,6 @@
   // If there is a pending read result, the OpenSSLErrorInfo associated with it.
   OpenSSLErrorInfo pending_read_error_info_;
 
-  // Used by TransportReadComplete() to signify an error reading from the
-  // transport socket. A value of OK indicates the socket is still
-  // readable. EOFs are mapped to ERR_CONNECTION_CLOSED.
-  int transport_read_error_;
-
-  // Used by TransportWriteComplete() and TransportReadComplete() to signify an
-  // error writing to the transport socket. A value of OK indicates no error.
-  int transport_write_error_;
-
   // Set when Connect finishes.
   std::unique_ptr<PeerCertificateChain> server_cert_chain_;
   scoped_refptr<X509Certificate> server_cert_;
@@ -336,9 +296,9 @@
 
   // OpenSSL stuff
   bssl::UniquePtr<SSL> ssl_;
-  bssl::UniquePtr<BIO> transport_bio_;
 
   std::unique_ptr<ClientSocketHandle> transport_;
+  std::unique_ptr<SocketBIOAdapter> transport_adapter_;
   const HostPortPair host_and_port_;
   SSLConfig ssl_config_;
   // ssl_session_cache_shard_ is an opaque string that partitions the SSL
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index a0ac613..a613dd9 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -1255,7 +1255,7 @@
 }
 
 // Tests that SSLClientSocket properly handles when the underlying transport
-// synchronously fails a transport read in during the handshake.
+// synchronously fails a transport write in during the handshake.
 TEST_F(SSLClientSocketTest, Connect_WithSynchronousError) {
   ASSERT_TRUE(StartTestServer(SpawnedTestServer::SSLOptions()));
 
@@ -1643,32 +1643,29 @@
 
   raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET);
 
-  // Write as much data as possible until hitting an error. This is necessary
-  // for NSS. PR_Write will only consume as much data as it can encode into
-  // application data records before the internal memio buffer is full, which
-  // should only fill if writing a large amount of data and the underlying
-  // transport is blocked. Once this happens, NSS will return (total size of all
-  // application data records it wrote) - 1, with the caller expected to resume
-  // with the remaining unsent data.
+  // Write as much data as possible until hitting an error.
   do {
     rv = callback.GetResult(sock->Write(long_request_buffer.get(),
                                         long_request_buffer->BytesRemaining(),
                                         callback.callback()));
     if (rv > 0) {
       long_request_buffer->DidConsume(rv);
-      // Abort if the entire buffer is ever consumed.
+      // Abort if the entire input is ever consumed. The input is larger than
+      // the SSLClientSocket's write buffers.
       ASSERT_LT(0, long_request_buffer->BytesRemaining());
     }
   } while (rv > 0);
 
   EXPECT_THAT(rv, IsError(ERR_CONNECTION_RESET));
 
-  // Release the read.
-  raw_transport->UnblockReadResult();
+  // At this point the Read result is available. Transport write errors are
+  // surfaced through Writes. See https://crbug.com/249848.
   rv = read_callback.WaitForResult();
+  EXPECT_THAT(rv, IsError(ERR_CONNECTION_RESET));
 
-  // Should still read bytes despite the write error.
-  EXPECT_LT(0, rv);
+  // Release the read. This does not cause a crash.
+  raw_transport->UnblockReadResult();
+  base::RunLoop().RunUntilIdle();
 }
 
 // Tests that SSLClientSocket fails the handshake if the underlying
diff --git a/net/socket/ssl_server_socket_impl.cc b/net/socket/ssl_server_socket_impl.cc
index 9eadcdb..c7d1e667 100644
--- a/net/socket/ssl_server_socket_impl.cc
+++ b/net/socket/ssl_server_socket_impl.cc
@@ -20,6 +20,7 @@
 #include "net/cert/x509_util_openssl.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_with_source.h"
+#include "net/socket/socket_bio_adapter.h"
 #include "net/ssl/openssl_ssl_util.h"
 #include "net/ssl/ssl_connection_status_flags.h"
 #include "net/ssl/ssl_info.h"
@@ -53,7 +54,8 @@
   return X509Certificate::CreateFromDERCertChain(der_chain);
 }
 
-class SSLServerSocketImpl : public SSLServerSocket {
+class SSLServerSocketImpl : public SSLServerSocket,
+                            public SocketBIOAdapter::Delegate {
  public:
   // See comments on CreateSSLServerSocket for details of how these
   // parameters are used.
@@ -101,29 +103,22 @@
   int64_t GetTotalReceivedBytes() const override;
   static int CertVerifyCallback(X509_STORE_CTX* store_ctx, void* arg);
 
+  // SocketBIOAdapter::Delegate implementation.
+  void OnReadReady() override;
+  void OnWriteReady() override;
+
  private:
   enum State {
     STATE_NONE,
     STATE_HANDSHAKE,
   };
 
-  void OnSendComplete(int result);
-  void OnRecvComplete(int result);
   void OnHandshakeIOComplete(int result);
 
-  int BufferSend();
-  void BufferSendComplete(int result);
-  void TransportWriteComplete(int result);
-  int BufferRecv();
-  void BufferRecvComplete(int result);
-  int TransportReadComplete(int result);
-  bool DoTransportIO();
   int DoPayloadRead();
   int DoPayloadWrite();
 
   int DoHandshakeLoop(int last_io_result);
-  int DoReadLoop(int result);
-  int DoWriteLoop(int result);
   int DoHandshake();
   void DoHandshakeCallback(int result);
   void DoReadCallback(int result);
@@ -132,14 +127,6 @@
   int Init();
   void ExtractClientCert();
 
-  // Members used to send and receive buffer.
-  bool transport_send_busy_;
-  bool transport_recv_busy_;
-  bool transport_recv_eof_;
-
-  scoped_refptr<DrainableIOBuffer> send_buffer_;
-  scoped_refptr<IOBuffer> recv_buffer_;
-
   NetLogWithSource net_log_;
 
   CompletionCallback user_handshake_callback_;
@@ -154,16 +141,12 @@
   scoped_refptr<IOBuffer> user_write_buf_;
   int user_write_buf_len_;
 
-  // Used by TransportWriteComplete() and TransportReadComplete() to signify an
-  // error writing to the transport socket. A value of OK indicates no error.
-  int transport_write_error_;
-
   // OpenSSL stuff
   bssl::UniquePtr<SSL> ssl_;
-  bssl::UniquePtr<BIO> transport_bio_;
 
   // StreamSocket for sending and receiving data.
   std::unique_ptr<StreamSocket> transport_socket_;
+  std::unique_ptr<SocketBIOAdapter> transport_adapter_;
 
   // Certificate for the client.
   scoped_refptr<X509Certificate> client_cert_;
@@ -177,12 +160,8 @@
 SSLServerSocketImpl::SSLServerSocketImpl(
     std::unique_ptr<StreamSocket> transport_socket,
     bssl::UniquePtr<SSL> ssl)
-    : transport_send_busy_(false),
-      transport_recv_busy_(false),
-      transport_recv_eof_(false),
-      user_read_buf_len_(0),
+    : user_read_buf_len_(0),
       user_write_buf_len_(0),
-      transport_write_error_(OK),
       ssl_(std::move(ssl)),
       transport_socket_(std::move(transport_socket)),
       next_handshake_state_(STATE_NONE),
@@ -261,7 +240,7 @@
 
   DCHECK(completed_handshake_);
 
-  int rv = DoReadLoop(OK);
+  int rv = DoPayloadRead();
 
   if (rv == ERR_IO_PENDING) {
     user_read_callback_ = callback;
@@ -283,7 +262,7 @@
   user_write_buf_ = buf;
   user_write_buf_len_ = buf_len;
 
-  int rv = DoWriteLoop(OK);
+  int rv = DoPayloadWrite();
 
   if (rv == ERR_IO_PENDING) {
     user_write_callback_ = callback;
@@ -394,45 +373,40 @@
   return transport_socket_->GetTotalReceivedBytes();
 }
 
-void SSLServerSocketImpl::OnSendComplete(int result) {
+void SSLServerSocketImpl::OnReadReady() {
   if (next_handshake_state_ == STATE_HANDSHAKE) {
-    // In handshake phase.
-    OnHandshakeIOComplete(result);
+    // In handshake phase. The parameter to OnHandshakeIOComplete is unused.
+    OnHandshakeIOComplete(OK);
     return;
   }
 
-  // TODO(byungchul): This state machine is not correct. Copy the state machine
-  // of SSLClientSocketImpl::OnSendComplete() which handles it better.
-  if (!completed_handshake_)
+  // BoringSSL does not support renegotiation as a server, so the only other
+  // operation blocked on Read is DoPayloadRead.
+  if (!user_read_buf_)
     return;
 
-  if (user_write_buf_) {
-    int rv = DoWriteLoop(result);
-    if (rv != ERR_IO_PENDING)
-      DoWriteCallback(rv);
-  } else {
-    // Ensure that any queued ciphertext is flushed.
-    DoTransportIO();
-  }
-}
-
-void SSLServerSocketImpl::OnRecvComplete(int result) {
-  if (next_handshake_state_ == STATE_HANDSHAKE) {
-    // In handshake phase.
-    OnHandshakeIOComplete(result);
-    return;
-  }
-
-  // Network layer received some data, check if client requested to read
-  // decrypted data.
-  if (!user_read_buf_ || !completed_handshake_)
-    return;
-
-  int rv = DoReadLoop(result);
+  int rv = DoPayloadRead();
   if (rv != ERR_IO_PENDING)
     DoReadCallback(rv);
 }
 
+void SSLServerSocketImpl::OnWriteReady() {
+  if (next_handshake_state_ == STATE_HANDSHAKE) {
+    // In handshake phase. The parameter to OnHandshakeIOComplete is unused.
+    OnHandshakeIOComplete(OK);
+    return;
+  }
+
+  // BoringSSL does not support renegotiation as a server, so the only other
+  // operation blocked on Read is DoPayloadWrite.
+  if (!user_write_buf_)
+    return;
+
+  int rv = DoPayloadWrite();
+  if (rv != ERR_IO_PENDING)
+    DoWriteCallback(rv);
+}
+
 void SSLServerSocketImpl::OnHandshakeIOComplete(int result) {
   int rv = DoHandshakeLoop(result);
   if (rv == ERR_IO_PENDING)
@@ -443,161 +417,13 @@
     DoHandshakeCallback(rv);
 }
 
-// Return 0 for EOF,
-// > 0 for bytes transferred immediately,
-// < 0 for error (or the non-error ERR_IO_PENDING).
-int SSLServerSocketImpl::BufferSend() {
-  if (transport_send_busy_)
-    return ERR_IO_PENDING;
-
-  if (!send_buffer_) {
-    // Get a fresh send buffer out of the send BIO.
-    size_t max_read = BIO_pending(transport_bio_.get());
-    if (!max_read)
-      return 0;  // Nothing pending in the OpenSSL write BIO.
-    send_buffer_ = new DrainableIOBuffer(new IOBuffer(max_read), max_read);
-    int read_bytes =
-        BIO_read(transport_bio_.get(), send_buffer_->data(), max_read);
-    DCHECK_GT(read_bytes, 0);
-    CHECK_EQ(static_cast<int>(max_read), read_bytes);
-  }
-
-  int rv = transport_socket_->Write(
-      send_buffer_.get(), send_buffer_->BytesRemaining(),
-      base::Bind(&SSLServerSocketImpl::BufferSendComplete,
-                 base::Unretained(this)));
-  if (rv == ERR_IO_PENDING) {
-    transport_send_busy_ = true;
-  } else {
-    TransportWriteComplete(rv);
-  }
-  return rv;
-}
-
-void SSLServerSocketImpl::BufferSendComplete(int result) {
-  transport_send_busy_ = false;
-  TransportWriteComplete(result);
-  OnSendComplete(result);
-}
-
-void SSLServerSocketImpl::TransportWriteComplete(int result) {
-  DCHECK(ERR_IO_PENDING != result);
-  if (result < 0) {
-    // Got a socket write error; close the BIO to indicate this upward.
-    //
-    // TODO(davidben): The value of |result| gets lost. Feed the error back into
-    // the BIO so it gets (re-)detected in OnSendComplete. Perhaps with
-    // BIO_set_callback.
-    DVLOG(1) << "TransportWriteComplete error " << result;
-    (void)BIO_shutdown_wr(SSL_get_wbio(ssl_.get()));
-
-    // Match the fix for http://crbug.com/249848 in NSS by erroring future reads
-    // from the socket after a write error.
-    //
-    // TODO(davidben): Avoid having read and write ends interact this way.
-    transport_write_error_ = result;
-    (void)BIO_shutdown_wr(transport_bio_.get());
-    send_buffer_ = NULL;
-  } else {
-    DCHECK(send_buffer_);
-    send_buffer_->DidConsume(result);
-    DCHECK_GE(send_buffer_->BytesRemaining(), 0);
-    if (send_buffer_->BytesRemaining() <= 0)
-      send_buffer_ = NULL;
-  }
-}
-
-int SSLServerSocketImpl::BufferRecv() {
-  if (transport_recv_busy_)
-    return ERR_IO_PENDING;
-
-  // Determine how much was requested from |transport_bio_| that was not
-  // actually available.
-  size_t requested = BIO_ctrl_get_read_request(transport_bio_.get());
-  if (requested == 0) {
-    // This is not a perfect match of error codes, as no operation is
-    // actually pending. However, returning 0 would be interpreted as
-    // a possible sign of EOF, which is also an inappropriate match.
-    return ERR_IO_PENDING;
-  }
-
-  // Known Issue: While only reading |requested| data is the more correct
-  // implementation, it has the downside of resulting in frequent reads:
-  // One read for the SSL record header (~5 bytes) and one read for the SSL
-  // record body. Rather than issuing these reads to the underlying socket
-  // (and constantly allocating new IOBuffers), a single Read() request to
-  // fill |transport_bio_| is issued. As long as an SSL client socket cannot
-  // be gracefully shutdown (via SSL close alerts) and re-used for non-SSL
-  // traffic, this over-subscribed Read()ing will not cause issues.
-  size_t max_write = BIO_ctrl_get_write_guarantee(transport_bio_.get());
-  if (!max_write)
-    return ERR_IO_PENDING;
-
-  recv_buffer_ = new IOBuffer(max_write);
-  int rv = transport_socket_->Read(
-      recv_buffer_.get(), max_write,
-      base::Bind(&SSLServerSocketImpl::BufferRecvComplete,
-                 base::Unretained(this)));
-  if (rv == ERR_IO_PENDING) {
-    transport_recv_busy_ = true;
-  } else {
-    rv = TransportReadComplete(rv);
-  }
-  return rv;
-}
-
-void SSLServerSocketImpl::BufferRecvComplete(int result) {
-  result = TransportReadComplete(result);
-  OnRecvComplete(result);
-}
-
-int SSLServerSocketImpl::TransportReadComplete(int result) {
-  DCHECK(ERR_IO_PENDING != result);
-  if (result <= 0) {
-    DVLOG(1) << "TransportReadComplete result " << result;
-    // Received 0 (end of file) or an error. Either way, bubble it up to the
-    // SSL layer via the BIO. TODO(joth): consider stashing the error code, to
-    // relay up to the SSL socket client (i.e. via DoReadCallback).
-    if (result == 0)
-      transport_recv_eof_ = true;
-    (void)BIO_shutdown_wr(transport_bio_.get());
-  } else if (transport_write_error_ < 0) {
-    // Mirror transport write errors as read failures; transport_bio_ has been
-    // shut down by TransportWriteComplete, so the BIO_write will fail, failing
-    // the CHECK. http://crbug.com/335557.
-    result = transport_write_error_;
-  } else {
-    DCHECK(recv_buffer_);
-    int ret = BIO_write(transport_bio_.get(), recv_buffer_->data(), result);
-    // A write into a memory BIO should always succeed.
-    DCHECK_EQ(result, ret);
-  }
-  recv_buffer_ = NULL;
-  transport_recv_busy_ = false;
-  return result;
-}
-
-// Do as much network I/O as possible between the buffer and the
-// transport socket. Return true if some I/O performed, false
-// otherwise (error or ERR_IO_PENDING).
-bool SSLServerSocketImpl::DoTransportIO() {
-  bool network_moved = false;
-  int rv;
-  // Read and write as much data as possible. The loop is necessary because
-  // Write() may return synchronously.
-  do {
-    rv = BufferSend();
-    if (rv != ERR_IO_PENDING && rv != 0)
-      network_moved = true;
-  } while (rv > 0);
-  if (!transport_recv_eof_ && BufferRecv() != ERR_IO_PENDING)
-    network_moved = true;
-  return network_moved;
-}
 
 int SSLServerSocketImpl::DoPayloadRead() {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_handshake_state_);
   DCHECK(user_read_buf_);
   DCHECK_GT(user_read_buf_len_, 0);
+
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
   int rv = SSL_read(ssl_.get(), user_read_buf_->data(), user_read_buf_len_);
   if (rv >= 0)
@@ -615,7 +441,10 @@
 }
 
 int SSLServerSocketImpl::DoPayloadWrite() {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_handshake_state_);
   DCHECK(user_write_buf_);
+
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
   int rv = SSL_write(ssl_.get(), user_write_buf_->data(), user_write_buf_len_);
   if (rv >= 0)
@@ -652,51 +481,10 @@
         LOG(DFATAL) << "unexpected state " << state;
         break;
     }
-
-    // Do the actual network I/O
-    bool network_moved = DoTransportIO();
-    if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
-      // In general we exit the loop if rv is ERR_IO_PENDING.  In this
-      // special case we keep looping even if rv is ERR_IO_PENDING because
-      // the transport IO may allow DoHandshake to make progress.
-      rv = OK;  // This causes us to stay in the loop.
-    }
   } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
   return rv;
 }
 
-int SSLServerSocketImpl::DoReadLoop(int result) {
-  DCHECK(completed_handshake_);
-  DCHECK(next_handshake_state_ == STATE_NONE);
-
-  if (result < 0)
-    return result;
-
-  bool network_moved;
-  int rv;
-  do {
-    rv = DoPayloadRead();
-    network_moved = DoTransportIO();
-  } while (rv == ERR_IO_PENDING && network_moved);
-  return rv;
-}
-
-int SSLServerSocketImpl::DoWriteLoop(int result) {
-  DCHECK(completed_handshake_);
-  DCHECK_EQ(next_handshake_state_, STATE_NONE);
-
-  if (result < 0)
-    return result;
-
-  bool network_moved;
-  int rv;
-  do {
-    rv = DoPayloadWrite();
-    network_moved = DoTransportIO();
-  } while (rv == ERR_IO_PENDING && network_moved);
-  return rv;
-}
-
 int SSLServerSocketImpl::DoHandshake() {
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
   int net_error = OK;
@@ -764,23 +552,22 @@
 }
 
 int SSLServerSocketImpl::Init() {
-  DCHECK(!transport_bio_);
+  static const int kBufferSize = 17 * 1024;
 
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
 
   if (!ssl_)
     return ERR_UNEXPECTED;
 
-  BIO* ssl_bio = nullptr;
-  BIO* transport_bio_raw = nullptr;
-  // 0 => use default buffer sizes.
-  if (!BIO_new_bio_pair(&ssl_bio, 0, &transport_bio_raw, 0))
-    return ERR_UNEXPECTED;
-  transport_bio_.reset(transport_bio_raw);
-  DCHECK(ssl_bio);
-  DCHECK(transport_bio_);
+  transport_adapter_.reset(new SocketBIOAdapter(
+      transport_socket_.get(), kBufferSize, kBufferSize, this));
+  BIO* transport_bio = transport_adapter_->bio();
 
-  SSL_set_bio(ssl_.get(), ssl_bio, ssl_bio);
+  BIO_up_ref(transport_bio);  // SSL_set0_rbio takes ownership.
+  SSL_set0_rbio(ssl_.get(), transport_bio);
+
+  BIO_up_ref(transport_bio);  // SSL_set0_wbio takes ownership.
+  SSL_set0_wbio(ssl_.get(), transport_bio);
 
   return OK;
 }
diff --git a/net/socket/ssl_server_socket_unittest.cc b/net/socket/ssl_server_socket_unittest.cc
index 8f22947..e6a71a1 100644
--- a/net/socket/ssl_server_socket_unittest.cc
+++ b/net/socket/ssl_server_socket_unittest.cc
@@ -787,7 +787,8 @@
 
   client_socket_->Disconnect();
 
-  EXPECT_THAT(handshake_callback.GetResult(server_ret), IsError(ERR_FAILED));
+  EXPECT_THAT(handshake_callback.GetResult(server_ret),
+              IsError(ERR_CONNECTION_CLOSED));
 }
 
 TEST_F(SSLServerSocketTest, HandshakeWithClientCertRequiredNotSuppliedCached) {
@@ -820,7 +821,8 @@
 
   client_socket_->Disconnect();
 
-  EXPECT_THAT(handshake_callback.GetResult(server_ret), IsError(ERR_FAILED));
+  EXPECT_THAT(handshake_callback.GetResult(server_ret),
+              IsError(ERR_CONNECTION_CLOSED));
   server_socket_->Disconnect();
 
   // Below, check that the cache didn't store the result of a failed handshake.
@@ -842,7 +844,8 @@
 
   client_socket_->Disconnect();
 
-  EXPECT_THAT(handshake_callback2.GetResult(server_ret2), IsError(ERR_FAILED));
+  EXPECT_THAT(handshake_callback2.GetResult(server_ret2),
+              IsError(ERR_CONNECTION_CLOSED));
 }
 
 TEST_F(SSLServerSocketTest, HandshakeWithWrongClientCertSupplied) {
diff --git a/net/ssl/openssl_ssl_util.h b/net/ssl/openssl_ssl_util.h
index 06fd4090..04bcf30 100644
--- a/net/ssl/openssl_ssl_util.h
+++ b/net/ssl/openssl_ssl_util.h
@@ -9,6 +9,7 @@
 
 #include <openssl/x509.h>
 
+#include "net/base/net_export.h"
 #include "net/cert/x509_certificate.h"
 #include "net/log/net_log_parameters_callback.h"
 
@@ -42,7 +43,9 @@
 // Note that |tracer| is not currently used in the implementation, but is passed
 // in anyway as this ensures the caller will clear any residual codes left on
 // the error stack.
-int MapOpenSSLError(int err, const crypto::OpenSSLErrStackTracer& tracer);
+NET_EXPORT_PRIVATE int MapOpenSSLError(
+    int err,
+    const crypto::OpenSSLErrStackTracer& tracer);
 
 // Helper struct to store information about an OpenSSL error stack entry.
 struct OpenSSLErrorInfo {
diff --git a/remoting/client/plugin/pepper_video_renderer_3d.cc b/remoting/client/plugin/pepper_video_renderer_3d.cc
index b60f9bd7..aff694a2 100644
--- a/remoting/client/plugin/pepper_video_renderer_3d.cc
+++ b/remoting/client/plugin/pepper_video_renderer_3d.cc
@@ -91,14 +91,22 @@
   DCHECK(event_handler);
   DCHECK(!event_handler_);
 
+  fallback_renderer_.SetPepperContext(instance, event_handler);
+
   event_handler_ = event_handler;
   pp_instance_ = instance;
 }
 
 void PepperVideoRenderer3D::OnViewChanged(const pp::View& view) {
+  fallback_renderer_.OnViewChanged(view);
+
   pp::Size size = view.GetRect().size();
   float scale = view.GetDeviceScale();
-  view_size_.set(ceilf(size.width() * scale), ceilf(size.height() * scale));
+  DCHECK_GT(scale, 0.0);
+  view_size_.set(std::min<int>(ceilf(size.width() * scale),
+                               gl_max_viewport_size_[0]),
+                 std::min<int>(ceilf(size.height() * scale),
+                               gl_max_viewport_size_[1]));
   graphics_.ResizeBuffers(view_size_.width(), view_size_.height());
 
   force_repaint_ = true;
@@ -106,12 +114,17 @@
 }
 
 void PepperVideoRenderer3D::EnableDebugDirtyRegion(bool enable) {
+  fallback_renderer_.EnableDebugDirtyRegion(enable);
   debug_dirty_region_ = enable;
 }
 
 bool PepperVideoRenderer3D::Initialize(
     const ClientContext& context,
     protocol::FrameStatsConsumer* stats_consumer) {
+  if (!fallback_renderer_.Initialize(context, stats_consumer)) {
+    LOG(FATAL) << "Failed to initialize fallback_renderer_";
+  }
+
   stats_consumer_ = stats_consumer;
 
   const int32_t context_attributes[] = {
@@ -167,6 +180,11 @@
   gles2_if_->BufferData(graphics_3d, GL_ARRAY_BUFFER, sizeof(kVertices),
                         kVertices, GL_STATIC_DRAW);
 
+  gles2_if_->GetIntegerv(
+      graphics_3d, GL_MAX_TEXTURE_SIZE, &gl_max_texture_size_);
+  gles2_if_->GetIntegerv(
+      graphics_3d, GL_MAX_VIEWPORT_DIMS, gl_max_viewport_size_);
+
   CheckGLError();
 
   return true;
@@ -174,6 +192,8 @@
 
 void PepperVideoRenderer3D::OnSessionConfig(
     const protocol::SessionConfig& config) {
+  fallback_renderer_.OnSessionConfig(config);
+
   PP_VideoProfile video_profile = PP_VIDEOPROFILE_VP8_ANY;
   switch (config.video_config().codec) {
     case protocol::ChannelConfig::CODEC_VP8:
@@ -212,6 +232,29 @@
 void PepperVideoRenderer3D::ProcessVideoPacket(
     std::unique_ptr<VideoPacket> packet,
     const base::Closure& done) {
+  if (!use_fallback_renderer_ &&
+      packet->format().has_screen_width() &&
+      packet->format().has_screen_height() &&
+      (packet->format().screen_width() > gl_max_texture_size_ ||
+       packet->format().screen_height() > gl_max_texture_size_)) {
+    use_fallback_renderer_ = true;
+    // Clear current instance and use fallback_renderer_.
+    current_picture_frames_.clear();
+    current_picture_.reset();
+    next_picture_frames_.clear();
+    next_picture_.reset();
+    decoded_frames_.clear();
+    pending_frames_.clear();
+    graphics_ = pp::Graphics3D();
+    video_decoder_ = pp::VideoDecoder();
+  }
+
+  if (use_fallback_renderer_) {
+    fallback_renderer_.GetVideoStub()->ProcessVideoPacket(
+        std::move(packet), done);
+    return;
+  }
+
   VideoPacket* packet_ptr = packet.get();
   std::unique_ptr<FrameTracker> frame_tracker(
       new FrameTracker(std::move(packet), stats_consumer_, done));
diff --git a/remoting/client/plugin/pepper_video_renderer_3d.h b/remoting/client/plugin/pepper_video_renderer_3d.h
index e1699ecf..23494640 100644
--- a/remoting/client/plugin/pepper_video_renderer_3d.h
+++ b/remoting/client/plugin/pepper_video_renderer_3d.h
@@ -18,6 +18,7 @@
 #include "ppapi/cpp/video_decoder.h"
 #include "ppapi/utility/completion_callback_factory.h"
 #include "remoting/client/plugin/pepper_video_renderer.h"
+#include "remoting/client/plugin/pepper_video_renderer_2d.h"
 #include "remoting/protocol/video_stub.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 
@@ -136,6 +137,11 @@
   // the |current_picture_| is rendered.
   std::list<std::unique_ptr<FrameTracker>> current_picture_frames_;
 
+  // The fallback software renderer, if input video packet size is larger than
+  // hardware limitation.
+  PepperVideoRenderer2D fallback_renderer_;
+  bool use_fallback_renderer_ = false;
+
   // Set to true if the screen has been resized and needs to be repainted.
   bool force_repaint_ = false;
 
@@ -158,6 +164,10 @@
 
   pp::CompletionCallbackFactory<PepperVideoRenderer3D> callback_factory_;
 
+  // The hardware limitation.
+  int gl_max_texture_size_;
+  int gl_max_viewport_size_[2];
+
   DISALLOW_COPY_AND_ASSIGN(PepperVideoRenderer3D);
 };
 
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index 4d399ef..3c839d91 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -326,6 +326,7 @@
     "validating_authenticator_unittest.cc",
     "video_frame_pump_unittest.cc",
     "webrtc_audio_source_adapter_unittest.cc",
+    "webrtc_frame_scheduler_unittest.cc",
     "webrtc_transport_unittest.cc",
   ]
 
diff --git a/remoting/protocol/connection_unittest.cc b/remoting/protocol/connection_unittest.cc
index 8c81430..fc063183 100644
--- a/remoting/protocol/connection_unittest.cc
+++ b/remoting/protocol/connection_unittest.cc
@@ -534,11 +534,12 @@
   Connect();
 
   base::TimeTicks start_time = base::TimeTicks::Now();
+  base::TimeTicks event_timestamp = base::TimeTicks::FromInternalValue(42);
 
   scoped_refptr<InputEventTimestampsSourceImpl> input_event_timestamps_source =
       new InputEventTimestampsSourceImpl();
   input_event_timestamps_source->OnEventReceived(
-      InputEventTimestamps{start_time, start_time});
+      InputEventTimestamps{event_timestamp, start_time});
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
@@ -554,30 +555,27 @@
   const FrameStats& stats =
       client_video_renderer_.GetFrameStatsConsumer()->received_stats().front();
 
-  EXPECT_TRUE(stats.host_stats.frame_size > 0);
+  EXPECT_GT(stats.host_stats.frame_size, 0);
 
-  EXPECT_TRUE(stats.host_stats.latest_event_timestamp == start_time);
-  EXPECT_TRUE(stats.host_stats.capture_delay != base::TimeDelta::Max());
-  EXPECT_TRUE(stats.host_stats.capture_overhead_delay !=
-              base::TimeDelta::Max());
-  EXPECT_TRUE(stats.host_stats.encode_delay != base::TimeDelta::Max());
-  EXPECT_TRUE(stats.host_stats.send_pending_delay != base::TimeDelta::Max());
+  EXPECT_EQ(stats.host_stats.latest_event_timestamp, event_timestamp);
+  EXPECT_NE(stats.host_stats.capture_delay, base::TimeDelta::Max());
+  EXPECT_NE(stats.host_stats.capture_overhead_delay, base::TimeDelta::Max());
+  EXPECT_NE(stats.host_stats.encode_delay, base::TimeDelta::Max());
+  EXPECT_NE(stats.host_stats.send_pending_delay, base::TimeDelta::Max());
 
   EXPECT_FALSE(stats.client_stats.time_received.is_null());
   EXPECT_FALSE(stats.client_stats.time_decoded.is_null());
   EXPECT_FALSE(stats.client_stats.time_rendered.is_null());
 
-  EXPECT_TRUE(start_time + stats.host_stats.capture_pending_delay +
-                  stats.host_stats.capture_delay +
-                  stats.host_stats.capture_overhead_delay +
-                  stats.host_stats.encode_delay +
-                  stats.host_stats.send_pending_delay <=
-              stats.client_stats.time_received);
-  EXPECT_TRUE(stats.client_stats.time_received <=
-              stats.client_stats.time_decoded);
-  EXPECT_TRUE(stats.client_stats.time_decoded <=
-              stats.client_stats.time_rendered);
-  EXPECT_TRUE(stats.client_stats.time_rendered <= finish_time);
+  EXPECT_LE(start_time + stats.host_stats.capture_pending_delay +
+                stats.host_stats.capture_delay +
+                stats.host_stats.capture_overhead_delay +
+                stats.host_stats.encode_delay +
+                stats.host_stats.send_pending_delay,
+            stats.client_stats.time_received);
+  EXPECT_LE(stats.client_stats.time_received, stats.client_stats.time_decoded);
+  EXPECT_LE(stats.client_stats.time_decoded, stats.client_stats.time_rendered);
+  EXPECT_LE(stats.client_stats.time_rendered, finish_time);
 }
 
 TEST_P(ConnectionTest, Audio) {
diff --git a/remoting/protocol/webrtc_dummy_video_encoder.h b/remoting/protocol/webrtc_dummy_video_encoder.h
index 4f59c14..92b63994 100644
--- a/remoting/protocol/webrtc_dummy_video_encoder.h
+++ b/remoting/protocol/webrtc_dummy_video_encoder.h
@@ -88,6 +88,10 @@
 
   void SetVideoChannelStateObserver(
       base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer);
+  base::WeakPtr<VideoChannelStateObserver>
+  get_video_channel_state_observer_for_tests() {
+    return video_channel_state_observer_;
+  }
 
  private:
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
diff --git a/remoting/protocol/webrtc_frame_scheduler_simple.cc b/remoting/protocol/webrtc_frame_scheduler_simple.cc
index 336276d..14d8724 100644
--- a/remoting/protocol/webrtc_frame_scheduler_simple.cc
+++ b/remoting/protocol/webrtc_frame_scheduler_simple.cc
@@ -64,8 +64,7 @@
 void WebrtcFrameSchedulerSimple::OnKeyFrameRequested() {
   DCHECK(thread_checker_.CalledOnValidThread());
   key_frame_request_ = true;
-  if (!capture_timer_.IsRunning())
-    ScheduleNextFrame(base::TimeTicks::Now());
+  ScheduleNextFrame(base::TimeTicks::Now());
 }
 
 void WebrtcFrameSchedulerSimple::OnChannelParameters(int packet_loss,
@@ -109,6 +108,7 @@
 
   if (frame.updated_region().is_empty() && !top_off_is_active_ &&
       !key_frame_request_) {
+    frame_pending_ = false;
     ScheduleNextFrame(now);
     return false;
   }
@@ -157,6 +157,8 @@
     const WebrtcVideoEncoder::EncodedFrame& encoded_frame,
     const webrtc::EncodedImageCallback::Result& send_result) {
   DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(frame_pending_);
+  frame_pending_ = false;
 
   base::TimeTicks now = base::TimeTicks::Now();
   pacing_bucket_.RefillOrSpill(encoded_frame.data.size(), now);
@@ -177,10 +179,10 @@
 void WebrtcFrameSchedulerSimple::ScheduleNextFrame(base::TimeTicks now) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  // Don't capture frames when paused or target bitrate is 0 or there is
-  // no capture callback set.
-  if (paused_ || pacing_bucket_.rate() == 0 || capture_callback_.is_null())
+  if (paused_ || pacing_bucket_.rate() == 0 || capture_callback_.is_null() ||
+      frame_pending_) {
     return;
+  }
 
   // If this is not the first frame then capture next frame after the previous
   // one has finished sending.
@@ -205,8 +207,9 @@
 
 void WebrtcFrameSchedulerSimple::CaptureNextFrame() {
   DCHECK(thread_checker_.CalledOnValidThread());
-
+  DCHECK(!frame_pending_);
   last_capture_started_time_ = base::TimeTicks::Now();
+  frame_pending_ = true;
   capture_callback_.Run();
 }
 
diff --git a/remoting/protocol/webrtc_frame_scheduler_simple.h b/remoting/protocol/webrtc_frame_scheduler_simple.h
index 6a38b39d..f5f3702 100644
--- a/remoting/protocol/webrtc_frame_scheduler_simple.h
+++ b/remoting/protocol/webrtc_frame_scheduler_simple.h
@@ -16,9 +16,10 @@
 namespace remoting {
 namespace protocol {
 
-// WebrtcFrameSchedulerSimple is a simple implementation of
-// WebrtcFrameScheduler that always keeps only one frame in the pipeline.
-// It schedules each frame after the previous one is expected to finish sending.
+// WebrtcFrameSchedulerSimple is a simple implementation of WebrtcFrameScheduler
+// that always keeps only one frame in the pipeline. It schedules each frame
+// such that it is encoded and ready to be sent by the time previous one is
+// expected to finish sending.
 class WebrtcFrameSchedulerSimple : public VideoChannelStateObserver,
                                    public WebrtcFrameScheduler {
  public:
@@ -53,6 +54,9 @@
 
   LeakyBucket pacing_bucket_;
 
+  // Set to true when a frame is being captured or encoded.
+  bool frame_pending_ = false;
+
   // Set to true when encoding unchanged frames for top-off.
   bool top_off_is_active_ = false;
 
diff --git a/remoting/protocol/webrtc_frame_scheduler_unittest.cc b/remoting/protocol/webrtc_frame_scheduler_unittest.cc
new file mode 100644
index 0000000..3a578e34
--- /dev/null
+++ b/remoting/protocol/webrtc_frame_scheduler_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/protocol/webrtc_frame_scheduler.h"
+
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "remoting/protocol/webrtc_dummy_video_encoder.h"
+#include "remoting/protocol/webrtc_frame_scheduler_simple.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+namespace protocol {
+
+class WebrtcFrameSchedulerTest : public ::testing::Test {
+ public:
+  WebrtcFrameSchedulerTest()
+      : task_runner_(new base::TestSimpleTaskRunner()),
+        task_runner_handle_(task_runner_.get()) {
+    video_encoder_factory_.reset(new WebrtcDummyVideoEncoderFactory());
+    scheduler_.reset(new WebrtcFrameSchedulerSimple());
+    scheduler_->Start(video_encoder_factory_.get(),
+                      base::Bind(&WebrtcFrameSchedulerTest::CaptureCallback,
+                                 base::Unretained(this)));
+  }
+  ~WebrtcFrameSchedulerTest() override {}
+
+  void CaptureCallback() { capture_callback_called_ = true; }
+
+  void VerifyCaptureCallbackCalled() {
+    EXPECT_TRUE(capture_callback_called_);
+    capture_callback_called_ = false;
+  }
+
+ protected:
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+
+  std::unique_ptr<WebrtcDummyVideoEncoderFactory> video_encoder_factory_;
+  std::unique_ptr<WebrtcFrameScheduler> scheduler_;
+
+  bool capture_callback_called_ = false;
+};
+
+TEST_F(WebrtcFrameSchedulerTest, UpdateBitrateWhenPending) {
+  auto video_channel_observer =
+      video_encoder_factory_->get_video_channel_state_observer_for_tests();
+
+  video_channel_observer->OnKeyFrameRequested();
+  video_channel_observer->OnTargetBitrateChanged(100);
+
+  EXPECT_TRUE(task_runner_->HasPendingTask());
+  task_runner_->RunPendingTasks();
+
+  VerifyCaptureCallbackCalled();
+
+  video_channel_observer->OnTargetBitrateChanged(1001);
+
+  // |task_runner_| shouldn't have pending tasks as the scheduler should be
+  // waiting for the previous capture request to complete.
+  EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+// TODO(sergeyu): Add more unittests.
+
+}  // namespace protocol
+}  // namespace remoting
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index 46a6f7e..dcc4e4c 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -231,7 +231,7 @@
           frame.timestamps->capture_started_time -
           frame.timestamps->input_event_timestamps.host_timestamp;
       stats.latest_event_timestamp =
-          frame.timestamps->input_event_timestamps.host_timestamp;
+          frame.timestamps->input_event_timestamps.client_timestamp;
     }
 
     stats.capture_delay = frame.timestamps->capture_delay;
diff --git a/services/service_manager/manifest.json b/services/service_manager/manifest.json
index 0d34e24c..8e4ba92 100644
--- a/services/service_manager/manifest.json
+++ b/services/service_manager/manifest.json
@@ -32,7 +32,9 @@
       ]
     },
     "required": {
-      "service:service_manager": [ "service_manager:all_users" ]
+      "*": [ "service_manager:service_factory" ],
+      "service:catalog": [ "service_manager:resolver" ],
+      "service:tracing": [ "app" ]
     }
   }
 }
diff --git a/services/service_manager/service_manager.cc b/services/service_manager/service_manager.cc
index aa46c32..59ab9430 100644
--- a/services/service_manager/service_manager.cc
+++ b/services/service_manager/service_manager.cc
@@ -506,6 +506,7 @@
       "service_manager::mojom::ServiceManager");
   spec.requires["*"].insert("service_manager:service_factory");
   spec.requires["service:catalog"].insert("service_manager:resolver");
+  spec.requires["service:tracing"].insert("app");
 
   service_manager_instance_ =
       CreateInstance(Identity(), CreateServiceManagerIdentity(), spec);
diff --git a/services/service_manager/standalone/context.cc b/services/service_manager/standalone/context.cc
index 55bc9f76..ed5c5f0 100644
--- a/services/service_manager/standalone/context.cc
+++ b/services/service_manager/standalone/context.cc
@@ -36,6 +36,7 @@
 #include "services/catalog/catalog.h"
 #include "services/catalog/store.h"
 #include "services/service_manager/connect_params.h"
+#include "services/service_manager/connect_util.h"
 #include "services/service_manager/public/cpp/names.h"
 #include "services/service_manager/runner/common/switches.h"
 #include "services/service_manager/runner/host/in_process_native_runner.h"
@@ -73,27 +74,6 @@
   DISALLOW_COPY_AND_ASSIGN(Setup);
 };
 
-class TracingInterfaceProvider : public mojom::InterfaceProvider {
- public:
-  explicit TracingInterfaceProvider(Tracer* tracer) : tracer_(tracer) {}
-  ~TracingInterfaceProvider() override {}
-
-  // mojom::InterfaceProvider:
-  void GetInterface(const std::string& interface_name,
-                    mojo::ScopedMessagePipeHandle client_handle) override {
-    if (tracer_ && interface_name == tracing::mojom::Provider::Name_) {
-      tracer_->ConnectToProvider(
-          mojo::MakeRequest<tracing::mojom::Provider>(
-              std::move(client_handle)));
-    }
-  }
-
- private:
-  Tracer* tracer_;
-
-  DISALLOW_COPY_AND_ASSIGN(TracingInterfaceProvider);
-};
-
 const size_t kMaxBlockingPoolThreads = 3;
 
 std::unique_ptr<base::Thread> CreateIOThread(const char* name) {
@@ -202,23 +182,18 @@
     }
   }
 
-  mojom::InterfaceProviderPtr tracing_remote_interfaces;
-  mojom::InterfaceProviderPtr tracing_local_interfaces;
-  mojo::MakeStrongBinding(base::MakeUnique<TracingInterfaceProvider>(&tracer_),
-                          mojo::GetProxy(&tracing_local_interfaces));
 
-  std::unique_ptr<ConnectParams> params(new ConnectParams);
-  params->set_source(CreateServiceManagerIdentity());
-  params->set_target(Identity("service:tracing", mojom::kRootUserID));
-  params->set_remote_interfaces(mojo::GetProxy(&tracing_remote_interfaces));
-  service_manager_->Connect(std::move(params));
+  Identity source_identity = CreateServiceManagerIdentity();
+  Identity tracing_identity("service:tracing", mojom::kRootUserID);
+  tracing::mojom::FactoryPtr factory;
+  ConnectToInterface(service_manager(), source_identity, tracing_identity,
+                     &factory);
+  provider_.InitializeWithFactory(&factory);
 
   if (command_line.HasSwitch(tracing::kTraceStartup)) {
     tracing::mojom::CollectorPtr coordinator;
-    auto coordinator_request = GetProxy(&coordinator);
-    tracing_remote_interfaces->GetInterface(
-        tracing::mojom::Collector::Name_,
-        coordinator_request.PassMessagePipe());
+    ConnectToInterface(service_manager(), source_identity, tracing_identity,
+                       &coordinator);
     tracer_.StartCollectingFromTracingService(std::move(coordinator));
   }
 
@@ -226,9 +201,8 @@
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           tracing::kEnableStatsCollectionBindings)) {
     tracing::mojom::StartupPerformanceDataCollectorPtr collector;
-    tracing_remote_interfaces->GetInterface(
-        tracing::mojom::StartupPerformanceDataCollector::Name_,
-        mojo::GetProxy(&collector).PassMessagePipe());
+    ConnectToInterface(service_manager(), source_identity, tracing_identity,
+                       &collector);
 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
     // CurrentProcessInfo::CreationTime is only defined on some platforms.
     const base::Time creation_time = base::CurrentProcessInfo::CreationTime();
diff --git a/services/service_manager/standalone/context.h b/services/service_manager/standalone/context.h
index 41b261fd..4522d39d 100644
--- a/services/service_manager/standalone/context.h
+++ b/services/service_manager/standalone/context.h
@@ -15,6 +15,7 @@
 #include "mojo/edk/embedder/process_delegate.h"
 #include "services/service_manager/service_manager.h"
 #include "services/service_manager/standalone/tracer.h"
+#include "services/tracing/public/cpp/provider.h"
 
 namespace base {
 class SingleThreadTaskRunner;
@@ -72,6 +73,7 @@
   // Ensure this is destructed before task_runners_ since it owns a message pipe
   // that needs the IO thread to destruct cleanly.
   Tracer tracer_;
+  tracing::Provider provider_;
   std::unique_ptr<catalog::Catalog> catalog_;
   std::unique_ptr<ServiceManager> service_manager_;
   base::Time main_entry_time_;
diff --git a/services/service_manager/standalone/tracer.cc b/services/service_manager/standalone/tracer.cc
index 0fa74a3..20fd888 100644
--- a/services/service_manager/standalone/tracer.cc
+++ b/services/service_manager/standalone/tracer.cc
@@ -64,10 +64,6 @@
     StopTracingAndFlushToDisk();
 }
 
-void Tracer::ConnectToProvider(tracing::mojom::ProviderRequest request) {
-  provider_.Bind(std::move(request));
-}
-
 void Tracer::StopTracingAndFlushToDisk() {
   tracing_ = false;
   trace_file_ = fopen(trace_filename_.c_str(), "w+");
diff --git a/services/service_manager/standalone/tracer.h b/services/service_manager/standalone/tracer.h
index 10bc60bf..3ee7986 100644
--- a/services/service_manager/standalone/tracer.h
+++ b/services/service_manager/standalone/tracer.h
@@ -15,7 +15,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
 #include "mojo/common/data_pipe_drainer.h"
-#include "services/tracing/public/cpp/provider.h"
 #include "services/tracing/public/interfaces/tracing.mojom.h"
 
 namespace service_manager {
@@ -78,7 +77,6 @@
   tracing::mojom::CollectorPtr coordinator_;
   std::unique_ptr<mojo::common::DataPipeDrainer> drainer_;
 
-  tracing::Provider provider_;
   // Whether we're currently tracing.
   bool tracing_;
   // Categories to trace.
diff --git a/services/tracing/public/cpp/provider.cc b/services/tracing/public/cpp/provider.cc
index 47e0ab0c..66e3a70 100644
--- a/services/tracing/public/cpp/provider.cc
+++ b/services/tracing/public/cpp/provider.cc
@@ -42,24 +42,10 @@
   StopTracing();
 }
 
-void Provider::Initialize(service_manager::Connector* connector,
-                          const std::string& url) {
-  {
-    base::AutoLock lock(g_singleton_lock.Get());
-    if (g_tracing_singleton_created)
-      return;
-    g_tracing_singleton_created = true;
-  }
-
-  // This will only set the name for the first app in a loaded mojo file. It's
-  // up to something like CoreServices to name its own child threads.
-  base::PlatformThread::SetName(url);
-
-  mojom::FactoryPtr factory;
-  connector->ConnectToInterface("service:tracing", &factory);
+void Provider::InitializeWithFactoryInternal(mojom::FactoryPtr* factory) {
   mojom::ProviderPtr provider;
   Bind(GetProxy(&provider));
-  factory->CreateRecorder(std::move(provider));
+  (*factory)->CreateRecorder(std::move(provider));
 #ifdef NDEBUG
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           tracing::kEarlyTracing)) {
@@ -70,6 +56,32 @@
 #endif
 }
 
+void Provider::InitializeWithFactory(mojom::FactoryPtr* factory) {
+  {
+    base::AutoLock lock(g_singleton_lock.Get());
+    if (g_tracing_singleton_created)
+      return;
+    g_tracing_singleton_created = true;
+  }
+  InitializeWithFactoryInternal(factory);
+}
+
+void Provider::Initialize(service_manager::Connector* connector,
+                          const std::string& url) {
+  {
+    base::AutoLock lock(g_singleton_lock.Get());
+    if (g_tracing_singleton_created)
+      return;
+    g_tracing_singleton_created = true;
+  }
+  mojom::FactoryPtr factory;
+  connector->ConnectToInterface("service:tracing", &factory);
+  InitializeWithFactoryInternal(&factory);
+  // This will only set the name for the first app in a loaded mojo file. It's
+  // up to something like CoreServices to name its own child threads.
+  base::PlatformThread::SetName(url);
+}
+
 void Provider::Bind(mojom::ProviderRequest request) {
   if (!binding_.is_bound()) {
     binding_.Bind(std::move(request));
diff --git a/services/tracing/public/cpp/provider.h b/services/tracing/public/cpp/provider.h
index 4b305d4..761efec 100644
--- a/services/tracing/public/cpp/provider.h
+++ b/services/tracing/public/cpp/provider.h
@@ -24,12 +24,15 @@
   Provider();
   ~Provider() override;
 
+  void InitializeWithFactory(mojom::FactoryPtr* factory);
   void Initialize(service_manager::Connector* connector,
                   const std::string& url);
 
   void Bind(mojom::ProviderRequest request);
 
  private:
+  void InitializeWithFactoryInternal(mojom::FactoryPtr* factory);
+
   // mojom::Provider implementation:
   void StartTracing(const std::string& categories,
                     mojom::RecorderPtr recorder) override;
diff --git a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
index 7418bb64..ec4b767 100644
--- a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
+++ b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
@@ -1,9 +1,6 @@
 -ActivityLogApiTest.TriggerEvent
 -BrowserActionApiTest.BrowserActionPopupWithIframe
 -ChromeServiceWorker*
--ContinueWhereILeftOffTest.PostWithPassword
--ContinueWhereILeftOffTest.PostWithPasswordBrowserClose
--ContinueWhereILeftOffTest.PostWithPasswordCloseAllBrowsers
 -CrazyExtensionTest.Crazy
 -DevToolsSanityTest.TestNetworkRawHeadersText
 -ErrorPageTest.IFrameDNSError_JavaScript
@@ -16,10 +13,9 @@
 -ExtensionBindingsApiTest.ModuleSystem
 -ExtensionBrowserTest.WindowOpenExtension
 -ExtensionResourceRequestPolicyTest.Iframe
--ExtensionWebRequestApiTest.WebRequestComplex
+# ExtensionWebRequestApiTest.WebRequestDeclarative2 crashes on bots but passes locally
 -ExtensionWebRequestApiTest.WebRequestDeclarative2
 -ExtensionWebRequestApiTest.WebRequestUnloadImmediately
--ExtensionWebRequestApiTest.WebRequestWithWithheldPermissions
 -ExternallyConnectableMessagingTest.FromPopup
 -FindInPageControllerTest.SearchWithinSpecialURL
 -IsolatedAppTest.CookieIsolation
@@ -36,7 +32,6 @@
 -PrerenderBrowserTest.PrerenderCrossProcessServerRedirect
 -PrerenderBrowserTest.PrerenderCrossProcessServerRedirectNoHang
 -RedirectTest.ClientServerServer
--RestartTest.PostWithPassword
 -SafeBrowsing*
 -SubresourceFilterBrowserTest.MainFrameActivation
 -SubresourceFilterBrowserTest.MainFrameActivationOnStartup
@@ -58,25 +53,16 @@
 -WebNavigationApiTest.RequestOpenTab
 -WebNavigationApiTest.SrcDoc
 -WebNavigationApiTest.UserAction
--WebViewTests/WebViewTest.Shim_TestDeclarativeWebRequestAPI/*
--WebViewTests/WebViewTest.Shim_TestDeclarativeWebRequestAPISendMessage/*
 -WebViewTests/WebViewTest.Shim_TestInlineScriptFromAccessibleResources/*
 -WebViewTests/WebViewTest.Shim_TestMailtoLink/*
 -WebViewTests/WebViewTest.Shim_TestNavigationToExternalProtocol/*
 -WebViewTests/WebViewTest.Shim_TestNestedSubframes/*
--WebViewTests/WebViewTest.Shim_TestWebRequestAPIErrorOccurred/*
--WebViewTests/WebViewTest.Shim_TestWebRequestAPIGoogleProperty/*
--WebViewTests/WebViewTest.Shim_TestWebRequestAPIWithHeaders/*
--WebViewTests/WebViewTest.Shim_TestWebRequestListenerSurvivesReparenting/*
 -WebViewTests/WebViewTest.Shim_TestWebViewInsideFrame/*
 
 # Errors related to forms submission.
 -PhishingDOMFeatureExtractorTest.SubFrames
 -PhishingDOMFeatureExtractorTest.SubframeRemoval
 
-# https://crbug.com/647712: Nested URL blocking needs to work with PlzNavigate
--ProcessManagerBrowserTest.NestedURLNavigationsToExtensionBlocked
-
 # Fail in Debug.
 -BookmarkBubbleSignInDelegateTest.*
 -GetAuthTokenFunctionTest.ComponentWithNormalClientId
diff --git a/testing/libfuzzer/efficient_fuzzer.md b/testing/libfuzzer/efficient_fuzzer.md
index fa1bc88a..fa7efa9 100644
--- a/testing/libfuzzer/efficient_fuzzer.md
+++ b/testing/libfuzzer/efficient_fuzzer.md
@@ -186,18 +186,28 @@
 
 ## Coverage
 
-You can easily generate source-level coverage report for a given corpus:
+[ClusterFuzz status] page provides fuzzer source-level coverage report 
+from the recent run. Looking at the report might provide an insight
+to improve fuzzer coverage.
 
-```
-ASAN_OPTIONS=html_cov_report=1:sancov_path=./third_party/llvm-build/Release+Asserts/bin/sancov \
-  ./out/libfuzzer/my_fuzzer -runs=0 ~/tmp/my_fuzzer_corpus
-```
+You can also access source-level coverage report locally:
 
-This will produce an .html file with colored source-code. It can be used to
-determine where your fuzzer is "stuck". Replace `ASAN_OPTIONS` by corresponding
-option variable if your are using another sanitizer (e.g. `MSAN_OPTIONS`).
-`sancov_path` can be omitted by adding llvm bin directory to `PATH` environment
-variable.
+```bash
+# produces binary .sancov file
+ASAN_OPTIONS=coverage=1 ./out/libfuzzer/my_fuzzer -runs=0 ~/tmp/my_fuzzer_corpus
+# Convert binary .sancov to symbolized .symcov file.
+./third_party/llvm-build/Release+Asserts/bin/sancov \
+  -symbolize my_fuzzer my_fuzzer.123.sancov > my_fuzzer.symcov
+# Launch coverage report server
+curl http://llvm.org/svn/llvm-project/llvm/trunk/tools/sancov/coverage-report-server.py | python3 \
+  --symcov my_fuzzer.symcov --srcpath path_to_chromium_sources
+# Navigate to http://localhost:8001/ to view coverage report
+```
+Replace `ASAN_OPTIONS` by corresponding option variable if your are using 
+another sanitizer (e.g. `MSAN_OPTIONS`).
+
+*NOTE: This is an experimental feature and an active area of work. We are 
+working on improving this process.*
 
 
 ## Corpus Size
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
index fe732fa2..83ea7fb 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
@@ -95,6 +95,9 @@
 # layout_test_runtime_flags_.have_top_loading_frame()
 crbug.com/645641 imported/wpt/html/syntax/parsing/html5lib_tests19.html [ Crash Failure ]
 
+# https://crbug.com/657081 - Allow-CSP-From header test (experimental feature)
+crbug.com/657081 http/tests/security/contentSecurityPolicy/embeddedEnforcement/allow_csp_from-header.html [ Failure ]
+
 # TODO(lukasza, alexmos): Triage these failures.
 Bug(none) fast/frames/cached-frame-counter.html [ Timeout ]
 Bug(none) fast/frames/frame-limit.html [ Timeout ]
diff --git a/third_party/WebKit/LayoutTests/fast/performance/longtasktiming.html b/third_party/WebKit/LayoutTests/fast/performance/longtasktiming.html
new file mode 100644
index 0000000..baa96a0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/performance/longtasktiming.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+
+async_test(function (t) {
+    var observer = new PerformanceObserver(
+        t.step_func(function (entryList) {
+            var entries = entryList.getEntries();
+            /* TODO(panicker): Update after fixing http://crbug.com/651461 */
+            for (var i = 0; i < entries.length; i++) {
+                assert_equals(entries[i].entryType, "longtask",
+                    "entryType expected to be: longtask");
+                assert_equals(entries[i].name, "same-origin",
+                    "name expected to be: same-origin");
+                assert_greater_than(entries[i].duration, 50000,
+                    "duration expected to be greater than 50ms threshold");
+            }
+            observer.disconnect();
+            t.done();
+        })
+    );
+    observer.observe({entryTypes: ["longtask"]});
+
+    /* Generate a slow task */
+    var begin = window.performance.now();
+    while (window.performance.now() < begin + 51);
+
+}, "Performance longtask entries are observable");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only-expected.txt b/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only-expected.txt
new file mode 100644
index 0000000..a28420d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only-expected.txt
@@ -0,0 +1,12 @@
+There should be a hotpink square below.
+
+
+
+
+
+
+
+
+
+
+PASS
diff --git a/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only.html b/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only.html
new file mode 100644
index 0000000..72c164f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fragmentation/break-in-first-table-row-only.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<style>
+    body { overflow: scroll; }
+</style>
+<p>There should be a hotpink square below.</p>
+<div id="multicol" style="position:relative; columns:2; column-fill:auto; column-gap:0; width:100px; height:90px; line-height:20px; orphans:1; widows:1;">
+    <br>
+    <br>
+    <br>
+    <table data-expected-height="110" cellspacing="0" cellpadding="0">
+        <col style="width:10px;">
+        <col style="width:10px;">
+        <tr data-expected-height="50">
+            <td data-expected-height="50">
+                <br><br>
+            </td>
+        </tr>
+        <tr data-expected-height="60">
+            <td data-expected-height="60">
+                <br>
+                <div data-offset-y="40" style="position:relative; background:hotpink;"><br></div>
+                <br>
+            </td>
+            <td>
+                <div data-offset-y="40" style="position:relative; height:20px; background:hotpink;"><br></div>
+            </td>
+        </tr>
+    </table>
+</div>
+<script src="../resources/check-layout.js"></script>
+<script>
+    checkLayout("#multicol");
+</script>
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-ref-expected.txt b/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-reference-expected.txt
similarity index 80%
rename from third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-ref-expected.txt
rename to third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-reference-expected.txt
index 7354e1ac..1980c439 100644
--- a/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-ref-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-reference-expected.txt
@@ -1,8 +1,6 @@
 Tests that debugging a page where Object prototype has a cyclic reference won't crash the browser.Bug 43558
 
-Debugger was enabled.
 Set timer for test function.
 Script execution paused.
 Script execution resumed.
-Debugger was disabled.
 
diff --git a/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-ref.html b/third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-reference.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-ref.html
rename to third_party/WebKit/LayoutTests/inspector/sources/debugger/debugger-cyclic-reference.html
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clear-key-invalid-license.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clear-key-invalid-license.html
index 55635f40..d28e520 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clear-key-invalid-license.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clear-key-invalid-license.html
@@ -20,7 +20,7 @@
                         assert_unreached('Error: update() succeeded unexpectedly.');
                         test.done();
                     }).catch(function(error) {
-                        assert_equals(error.name, 'InvalidAccessError');
+                        assert_equals(error.name, 'TypeError');
                         test.done();
                     });
                 }
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clearkey-update-non-ascii-input.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clearkey-update-non-ascii-input.html
index da04aed5..c7a39e4 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clearkey-update-non-ascii-input.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-clearkey-update-non-ascii-input.html
@@ -28,7 +28,7 @@
                     mediaKeySession.update(stringToUint8Array(jwkSet)).then(function() {
                         forceTestFailureFromPromise(test, 'Error: update() succeeded');
                     }, function(error) {
-                        assert_equals(error.name, 'InvalidAccessError');
+                        assert_equals(error.name, 'TypeError');
                         test.done();
                     });
                 }
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-generate-request-disallowed-input.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-generate-request-disallowed-input.html
index 9c1dac6..fc37312 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-generate-request-disallowed-input.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-generate-request-disallowed-input.html
@@ -10,7 +10,7 @@
         <script>
             // Create a session and call generateRequest() with |initDataType|
             // and |initData|. generateRequest() should fail with an
-            // InvalidAccessError. Returns a promise that resolves successfully
+            // TypeError. Returns a promise that resolves successfully
             // if the error happened, rejects otherwise.
             function test_session(initDataType, initData)
             {
@@ -27,7 +27,7 @@
                     }).then(function() {
                         assert_unreached('generateRequest() succeeded');
                     }, function(error) {
-                        assert_equals(error.name, 'InvalidAccessError');
+                        assert_equals(error.name, 'TypeError');
                         return Promise.resolve('success');
                     });
                 })
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-not-callable-after-close.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-not-callable-after-close.html
new file mode 100644
index 0000000..9413cf75
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-not-callable-after-close.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Test MediaKeySession not callable after close().</title>
+        <script src="encrypted-media-utils.js"></script>
+        <script src="../../resources/testharness.js"></script>
+        <script src="../../resources/testharnessreport.js"></script>
+    </head>
+    <body>
+        <script>
+            // After a MediaKeySession object is closed, other operations
+            // (except calling close() again) on the session are not allowed
+            // and should return an InvalidStateError.
+
+            // Create, initialize, and close a session. Returns a promise that
+            // resolves with the created and closed mediaKeySession and the
+            // initDataType used.
+            function createClosedSession() {
+                var initDataType;
+                var initData;
+                var mediaKeySession;
+
+                return navigator.requestMediaKeySystemAccess('org.w3.clearkey', getSimpleConfiguration()).then(function(access) {
+                    initDataType = access.getConfiguration().initDataTypes[0];
+                    initData = getInitData(initDataType);
+                    return access.createMediaKeys();
+                }).then(function(mediaKeys) {
+                    mediaKeySession = mediaKeys.createSession();
+                    return mediaKeySession.generateRequest(initDataType, initData);
+                }).then(function() {
+                    return mediaKeySession.close();
+                }).then(function(result) {
+                    return Promise.resolve({ session: mediaKeySession, initDataType: initDataType} );
+                });
+            }
+
+            promise_test(function()
+            {
+                return createClosedSession().then(function(result) {
+                    var mediaKeySession = result.session;
+                    var initDataType = result.initDataType;
+                    var initData = getInitData(initDataType);
+
+                    // Now try the operation that should fail.
+                    return mediaKeySession.generateRequest(initDataType, initData);
+                }).then(function() {
+                    assert_unreached('Unexpected generateRequest() success.');
+                }, function(error) {
+                    assert_equals(error.name, 'InvalidStateError');
+                    return Promise.resolve(null);
+                });
+            }, 'generateRequest() after close().');
+
+            promise_test(function()
+            {
+                return createClosedSession().then(function(result) {
+                    var mediaKeySession = result.session;
+
+                    // Now try the operation that should fail.
+                    return mediaKeySession.load('1234');
+                }).then(function() {
+                    assert_unreached('Unexpected load() success.');
+                }, function(error) {
+                    assert_equals(error.name, 'InvalidStateError');
+                    return Promise.resolve(null);
+                });
+            }, 'load() after close().');
+
+            promise_test(function()
+            {
+                return createClosedSession().then(function(result) {
+                    var mediaKeySession = result.session;
+
+                    // Now try the operation that should fail.
+                    var validLicense = stringToUint8Array(createJWKSet(createJWK(stringToUint8Array('123'), stringToUint8Array('1234567890abcdef'))));
+                    return mediaKeySession.update(validLicense);
+                }).then(function() {
+                    assert_unreached('Unexpected update() success.');
+                }, function(error) {
+                    assert_equals(error.name, 'InvalidStateError');
+                    return Promise.resolve(null);
+                });
+            }, 'update() after close().');
+
+            promise_test(function()
+            {
+                return createClosedSession().then(function(result) {
+                    var mediaKeySession = result.session;
+
+                    // Now try the operation that should fail.
+                    return mediaKeySession.remove();
+                }).then(function() {
+                    assert_unreached('Unexpected remove() success.');
+                }, function(error) {
+                    assert_equals(error.name, 'InvalidStateError');
+                    return Promise.resolve(null);
+                });
+            }, 'remove() after close().');
+
+            promise_test(function()
+            {
+                return createClosedSession().then(function(result) {
+                    var mediaKeySession = result.session;
+
+                    // Now try calling close() again, which should succeed.
+                    return mediaKeySession.close();
+                });
+            }, 'close() after close().');
+        </script>
+    </body>
+</html>
+
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-requestmediakeysystemaccess.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-requestmediakeysystemaccess.html
index a9b85da..49cd47e 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-requestmediakeysystemaccess.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-requestmediakeysystemaccess.html
@@ -38,7 +38,7 @@
             }
 
             // Tests for keySystem.
-            expect_error('', [{}], 'InvalidAccessError', 'Empty keySystem');
+            expect_error('', [{}], 'TypeError', 'Empty keySystem');
             expect_error('com.example.unsupported', [{}], 'NotSupportedError', 'Unsupported keySystem');
             expect_error('org.w3.clearkey.', [{}], 'NotSupportedError', 'keySystem ends with "."');
             expect_error('org.w3.ClearKey', [{}], 'NotSupportedError', 'Capitalized keySystem');
@@ -77,7 +77,7 @@
             expect_error('Org.w3.clearkey', [{}], 'NotSupportedError', 'Key system name is case sensitive');
 
             // Tests for trivial configurations.
-            expect_error('org.w3.clearkey', [], 'InvalidAccessError', 'Empty supportedConfigurations');
+            expect_error('org.w3.clearkey', [], 'TypeError', 'Empty supportedConfigurations');
             expect_config('org.w3.clearkey', [{}], {}, 'Empty configuration');
 
             // Various combinations of supportedConfigurations.
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-syntax.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-syntax.html
index 201ee32..bae01004 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-syntax.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-syntax.html
@@ -72,11 +72,11 @@
                     func: function() { return navigator.requestMediaKeySystemAccess(1, [{}]); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function() { return navigator.requestMediaKeySystemAccess(new Uint8Array(0), [{}]); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function() { return navigator.requestMediaKeySystemAccess('', [{}]); }
                 },
                 {
@@ -90,7 +90,7 @@
                 },
                 // Empty sequence of MediaKeySystemConfiguration.
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function() { return navigator.requestMediaKeySystemAccess('org.w3.clearkey', []); }
                 },
                 // Invalid sequences of MediaKeySystemConfigurations.
@@ -283,7 +283,7 @@
                 },
                 // Invalid parameters.
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk8, _, initData) { return mk8.createSession().generateRequest('', initData); }
                 },
                 // Not supported initDataTypes.
@@ -300,7 +300,7 @@
                     func: function(mk11, _, initData) { return mk11.createSession().generateRequest(1, initData); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk12, _, initData) { return mk12.createSession().generateRequest(new Uint8Array(0), initData); }
                 },
                 {
@@ -356,7 +356,7 @@
                     func: function(mk5, type) { return mk5.createSession().generateRequest(type, 1); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk6, type) { return mk6.createSession().generateRequest(type, new Uint8Array(0)); }
                 }
             ];
@@ -413,21 +413,21 @@
                     func: function(mk1) { return mk1.createSession('temporary').load(); }
                 },
                 // 'temporary' sessions are never allowed, so always return
-                // 'InvalidAccessError'.
+                // 'TypeError'.
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk3) { return mk3.createSession('temporary').load(''); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk4) { return mk4.createSession('temporary').load(1); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk5) { return mk5.createSession('temporary').load('!@#$%^&*()'); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk6) { return mk6.createSession('temporary').load('1234'); }
                 }
             ];
@@ -616,7 +616,7 @@
                     func: function(s) { return s.update(1); }
                 },
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(s) { return s.update(new Uint8Array(0)); }
                 }
             ];
@@ -822,7 +822,7 @@
                 }).then(function(result) {
                     assert_unreached('remove() should not succeed for temporary sessions');
                 }, function(error) {
-                    assert_equals(error.name, 'InvalidAccessError');
+                    assert_equals(error.name, 'TypeError');
                 });
             }
 
@@ -933,7 +933,7 @@
                 },
                 // Empty array.
                 {
-                    exception: 'InvalidAccessError',
+                    exception: 'TypeError',
                     func: function(mk) { return mk.setServerCertificate(new Uint8Array(0)); }
                 }
             ];
diff --git a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-update-disallowed-input.html b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-update-disallowed-input.html
index e605bf66..2467aea 100644
--- a/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-update-disallowed-input.html
+++ b/third_party/WebKit/LayoutTests/media/encrypted-media/encrypted-media-update-disallowed-input.html
@@ -48,7 +48,7 @@
                     mediaKeySession.update(jwkSetArray).then(function() {
                         forceTestFailureFromPromise(test, 'Error: update() succeeded');
                     }, function(error) {
-                        assert_equals(error.name, 'InvalidAccessError');
+                        assert_equals(error.name, 'TypeError');
                         test.done();
                     });
                 }
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move-expected.html
new file mode 100644
index 0000000..6368453d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move-expected.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+Tests paint invalidation of list markers when the list is moved.
+<ul id="ulist" style="position: absolute; top: 300px; width: 300px">
+  <li>List item</li>
+</ul>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move.html b/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move.html
new file mode 100644
index 0000000..964a712
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/list-marker-move.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+Tests paint invalidation of list markers when the list is moved.
+<ul id="ulist" style="position: absolute; top: 100px; width: 300px">
+  <li>List item</li>
+</ul>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script>
+runAfterLayoutAndPaint(function() {
+  ulist.style.top = '300px';
+}, true);
+</script>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index d37c032..d9674b2 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -15,6 +15,16 @@
           "object": "LayoutBlockFlow (positioned) UL id='submenu'",
           "rect": [48, -156, 40, 20],
           "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [70, 94, 7, 19],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [70, -156, 7, 19],
+          "reason": "bounds change"
         }
       ]
     },
@@ -64,7 +74,7 @@
     },
     {
       "object": "LayoutListMarker (anonymous)",
-      "reason": "location change"
+      "reason": "bounds change"
     }
   ]
 }
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index a044310a..1cbc2db7 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -15,6 +15,16 @@
           "object": "LayoutBlockFlow (positioned) UL id='submenu'",
           "rect": [48, -158, 40, 18],
           "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, 92, 7, 18],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, -158, 7, 18],
+          "reason": "bounds change"
         }
       ]
     },
@@ -64,7 +74,7 @@
     },
     {
       "object": "LayoutListMarker (anonymous)",
-      "reason": "location change"
+      "reason": "bounds change"
     }
   ]
 }
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
index e7344e8..f87ab4c 100644
--- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/offset-change-wrong-invalidation-with-float-expected.txt
@@ -15,6 +15,16 @@
           "object": "LayoutBlockFlow (positioned) UL id='submenu'",
           "rect": [48, -158, 40, 18],
           "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, 92, 7, 17],
+          "reason": "bounds change"
+        },
+        {
+          "object": "LayoutListMarker (anonymous)",
+          "rect": [71, -158, 7, 17],
+          "reason": "bounds change"
         }
       ]
     },
@@ -64,7 +74,7 @@
     },
     {
       "object": "LayoutListMarker (anonymous)",
-      "reason": "location change"
+      "reason": "bounds change"
     }
   ]
 }
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptWrappableVisitor.cpp b/third_party/WebKit/Source/bindings/core/v8/ScriptWrappableVisitor.cpp
index 1ed50fa..b72b7ff 100644
--- a/third_party/WebKit/Source/bindings/core/v8/ScriptWrappableVisitor.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/ScriptWrappableVisitor.cpp
@@ -27,6 +27,9 @@
 
 void ScriptWrappableVisitor::TracePrologue(
     v8::EmbedderReachableReferenceReporter* reporter) {
+  // This CHECK ensures that wrapper tracing is not started from scopes
+  // that forbid GC execution, e.g., constructors.
+  CHECK(!ThreadState::current()->isGCForbidden());
   performCleanup();
 
   DCHECK(!m_tracingInProgress);
diff --git a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueFuzzer.cpp b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueFuzzer.cpp
index 8144745..e9cd65b 100644
--- a/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueFuzzer.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValueFuzzer.cpp
@@ -10,6 +10,7 @@
 #include "core/testing/DummyPageHolder.h"
 #include "platform/RuntimeEnabledFeatures.h"
 #include "platform/testing/BlinkFuzzerTestSupport.h"
+#include "platform/testing/UnitTestHelpers.h"
 #include "public/platform/WebBlobInfo.h"
 #include "public/platform/WebMessagePortChannel.h"
 #include "wtf/StringHasher.h"
@@ -93,9 +94,16 @@
   RefPtr<SerializedScriptValue> serializedScriptValue =
       SerializedScriptValue::create(reinterpret_cast<const char*>(data), size);
   serializedScriptValue->deserialize(isolate, messagePorts, blobs);
-
-  // Clean up.
   CHECK(!tryCatch.HasCaught())
       << "deserialize() should return null rather than throwing an exception.";
+
+  // Clean up. We have to periodically run pending tasks so that scheduled
+  // Oilpan GC occurs.
+  static int iterations = 0;
+  if (iterations++ == 2048) {
+    testing::runPendingTasks();
+    iterations = 0;
+  }
+
   return 0;
 }
diff --git a/third_party/WebKit/Source/core/dom/ContainerNode.h b/third_party/WebKit/Source/core/dom/ContainerNode.h
index 9f5e2c5b..ea945df 100644
--- a/third_party/WebKit/Source/core/dom/ContainerNode.h
+++ b/third_party/WebKit/Source/core/dom/ContainerNode.h
@@ -26,6 +26,7 @@
 #define ContainerNode_h
 
 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
+#include "bindings/core/v8/ScriptWrappableVisitor.h"
 #include "core/CoreExport.h"
 #include "core/dom/Node.h"
 #include "core/html/CollectionType.h"
@@ -311,8 +312,14 @@
       const QualifiedName* attrName = nullptr,
       Element* attributeOwnerElement = nullptr);
 
-  void setFirstChild(Node* child) { m_firstChild = child; }
-  void setLastChild(Node* child) { m_lastChild = child; }
+  void setFirstChild(Node* child) {
+    m_firstChild = child;
+    ScriptWrappableVisitor::writeBarrier(this, m_firstChild);
+  }
+  void setLastChild(Node* child) {
+    m_lastChild = child;
+    ScriptWrappableVisitor::writeBarrier(this, m_lastChild);
+  }
 
   // Utility functions for NodeListsNodeData API.
   template <typename Collection>
diff --git a/third_party/WebKit/Source/core/dom/DOMMatrix.cpp b/third_party/WebKit/Source/core/dom/DOMMatrix.cpp
index eed0a17..0e77259c 100644
--- a/third_party/WebKit/Source/core/dom/DOMMatrix.cpp
+++ b/third_party/WebKit/Source/core/dom/DOMMatrix.cpp
@@ -87,6 +87,25 @@
     m_is2D = value;
 }
 
+void DOMMatrix::setNAN() {
+  m_matrix->setM11(NAN);
+  m_matrix->setM12(NAN);
+  m_matrix->setM13(NAN);
+  m_matrix->setM14(NAN);
+  m_matrix->setM21(NAN);
+  m_matrix->setM22(NAN);
+  m_matrix->setM23(NAN);
+  m_matrix->setM24(NAN);
+  m_matrix->setM31(NAN);
+  m_matrix->setM32(NAN);
+  m_matrix->setM33(NAN);
+  m_matrix->setM34(NAN);
+  m_matrix->setM41(NAN);
+  m_matrix->setM42(NAN);
+  m_matrix->setM43(NAN);
+  m_matrix->setM44(NAN);
+}
+
 DOMMatrix* DOMMatrix::multiplySelf(DOMMatrixInit& other,
                                    ExceptionState& exceptionState) {
   DOMMatrix* otherMatrix = DOMMatrix::fromMatrix(other, exceptionState);
@@ -190,22 +209,7 @@
   if (m_matrix->isInvertible()) {
     m_matrix = TransformationMatrix::create(m_matrix->inverse());
   } else {
-    setM11(NAN);
-    setM12(NAN);
-    setM13(NAN);
-    setM14(NAN);
-    setM21(NAN);
-    setM22(NAN);
-    setM23(NAN);
-    setM24(NAN);
-    setM31(NAN);
-    setM32(NAN);
-    setM33(NAN);
-    setM34(NAN);
-    setM41(NAN);
-    setM42(NAN);
-    setM43(NAN);
-    setM44(NAN);
+    setNAN();
     setIs2D(false);
   }
   return this;
diff --git a/third_party/WebKit/Source/core/dom/DOMMatrix.h b/third_party/WebKit/Source/core/dom/DOMMatrix.h
index 782d3ae..0508392 100644
--- a/third_party/WebKit/Source/core/dom/DOMMatrix.h
+++ b/third_party/WebKit/Source/core/dom/DOMMatrix.h
@@ -106,6 +106,7 @@
   DOMMatrix(T sequence, int size);
 
   void setIs2D(bool value);
+  void setNAN();
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/LiveNodeList.h b/third_party/WebKit/Source/core/dom/LiveNodeList.h
index fd3110d..f7501dae 100644
--- a/third_party/WebKit/Source/core/dom/LiveNodeList.h
+++ b/third_party/WebKit/Source/core/dom/LiveNodeList.h
@@ -46,7 +46,12 @@
       : LiveNodeListBase(ownerNode,
                          rootType,
                          invalidationType,
-                         collectionType) {}
+                         collectionType) {
+    // Keep this in the child class because |registerNodeList| requires wrapper
+    // tracing and potentially calls virtual methods which is not allowed in a
+    // base class constructor.
+    document().registerNodeList(this);
+  }
 
   unsigned length() const final;
   Element* item(unsigned offset) const final;
diff --git a/third_party/WebKit/Source/core/dom/LiveNodeListBase.h b/third_party/WebKit/Source/core/dom/LiveNodeListBase.h
index cdc229e..e121e68 100644
--- a/third_party/WebKit/Source/core/dom/LiveNodeListBase.h
+++ b/third_party/WebKit/Source/core/dom/LiveNodeListBase.h
@@ -52,8 +52,6 @@
     DCHECK_EQ(m_rootType, static_cast<unsigned>(rootType));
     DCHECK_EQ(m_invalidationType, static_cast<unsigned>(invalidationType));
     DCHECK_EQ(m_collectionType, static_cast<unsigned>(collectionType));
-
-    document().registerNodeList(this);
   }
 
   virtual ~LiveNodeListBase() {}
diff --git a/third_party/WebKit/Source/core/frame/Settings.in b/third_party/WebKit/Source/core/frame/Settings.in
index 198c3a5..6892fa4 100644
--- a/third_party/WebKit/Source/core/frame/Settings.in
+++ b/third_party/WebKit/Source/core/frame/Settings.in
@@ -217,13 +217,10 @@
 # crbug.com/304873 tracks removal once it's been enabled on all platforms.
 touchEditingEnabled initial=false
 
-# Settings for experimental desktop pinch-zoom support (with semantics
-# optimized for large screens).  Pinch-zoom generally is implemented mainly
-# outside of blink (in the compositor) and doesn't require any settings.
-# These settings are for an experimental modification to how pinch-zoom
-# behaves. crbug.com/304869 tracks removal.
+# If true, scrollers will use overlay scrollbars.  These do not take up any
+# layout width, are drawn using solid color quads by the compositor, and fade away
+# after a timeout.
 useSolidColorScrollbars initial=false
-pinchOverlayScrollbarThickness type=int, initial=0
 
 # Experiment to have all APIs reflect the layout viewport.
 # crbug.com/489206 tracks the experiment.
@@ -432,4 +429,4 @@
 
 # Spellchecking is enabled by default for elements that do not specify it explicitly
 # using the "spellcheck" attribute.
-spellCheckEnabledByDefault initial=true
\ No newline at end of file
+spellCheckEnabledByDefault initial=true
diff --git a/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp b/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp
index dd061cd1..e429ce978f 100644
--- a/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp
+++ b/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp
@@ -46,14 +46,15 @@
 bool CSPSourceList::matches(
     const KURL& url,
     ResourceRequest::RedirectStatus redirectStatus) const {
-  // Wildcards match network schemes ('http', 'https', 'ws', 'wss'), and the
-  // scheme of the protected resource:
-  // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression.  Other
+  // Wildcards match network schemes ('http', 'https', 'ftp', 'ws', 'wss'), and
+  // the scheme of the protected resource:
+  // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression. Other
   // schemes, including custom schemes, must be explicitly listed in a source
   // list.
   if (m_allowStar) {
-    if (url.protocolIsInHTTPFamily() || url.protocolIs("ws") ||
-        url.protocolIs("wss") || m_policy->protocolMatchesSelf(url))
+    if (url.protocolIsInHTTPFamily() || url.protocolIs("ftp") ||
+        url.protocolIs("ws") || url.protocolIs("wss") ||
+        m_policy->protocolMatchesSelf(url))
       return true;
 
     return hasSourceMatchInList(url, redirectStatus);
diff --git a/third_party/WebKit/Source/core/frame/csp/CSPSourceListTest.cpp b/third_party/WebKit/Source/core/frame/csp/CSPSourceListTest.cpp
index 0c611d8c..f75e95a 100644
--- a/third_party/WebKit/Source/core/frame/csp/CSPSourceListTest.cpp
+++ b/third_party/WebKit/Source/core/frame/csp/CSPSourceListTest.cpp
@@ -75,6 +75,7 @@
   EXPECT_TRUE(sourceList.matches(KURL(base, "http://example.com/bar")));
   EXPECT_TRUE(sourceList.matches(KURL(base, "http://foo.example.com/")));
   EXPECT_TRUE(sourceList.matches(KURL(base, "http://foo.example.com/bar")));
+  EXPECT_TRUE(sourceList.matches(KURL(base, "ftp://example.com/")));
 
   EXPECT_FALSE(sourceList.matches(KURL(base, "data:https://example.test/")));
   EXPECT_FALSE(sourceList.matches(KURL(base, "blob:https://example.test/")));
diff --git a/third_party/WebKit/Source/core/html/HTMLCollection.cpp b/third_party/WebKit/Source/core/html/HTMLCollection.cpp
index 53eab77..10a1735 100644
--- a/third_party/WebKit/Source/core/html/HTMLCollection.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLCollection.cpp
@@ -169,7 +169,12 @@
                        type),
       m_overridesItemAfter(itemAfterOverrideType == OverridesItemAfter),
       m_shouldOnlyIncludeDirectChildren(
-          shouldTypeOnlyIncludeDirectChildren(type)) {}
+          shouldTypeOnlyIncludeDirectChildren(type)) {
+  // Keep this in the child class because |registerNodeList| requires wrapper
+  // tracing and potentially calls virtual methods which is not allowed in a
+  // base class constructor.
+  document().registerNodeList(this);
+}
 
 HTMLCollection* HTMLCollection::create(ContainerNode& base,
                                        CollectionType type) {
diff --git a/third_party/WebKit/Source/core/html/HTMLInputElement.cpp b/third_party/WebKit/Source/core/html/HTMLInputElement.cpp
index 7ee21b5..232fda4 100644
--- a/third_party/WebKit/Source/core/html/HTMLInputElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLInputElement.cpp
@@ -78,6 +78,7 @@
 
 namespace blink {
 
+using ValueMode = InputType::ValueMode;
 using namespace HTMLNames;
 
 class ListAttributeTargetObserver : public IdTargetObserver {
@@ -409,7 +410,8 @@
   InputType* newType = InputType::create(*this, newTypeName);
   removeFromRadioButtonGroup();
 
-  bool didStoreValue = m_inputType->storesValueSeparateFromAttribute();
+  bool didStoreValue = m_inputType->valueMode() == ValueMode::kValue ||
+                       m_inputType->valueMode() == ValueMode::kFilename;
   bool didRespectHeightAndWidth =
       m_inputType->shouldRespectHeightAndWidthAttributes();
   bool couldBeSuccessfulSubmitButton = canBeSuccessfulSubmitButton();
@@ -423,7 +425,8 @@
 
   setNeedsWillValidateCheck();
 
-  bool willStoreValue = m_inputType->storesValueSeparateFromAttribute();
+  bool willStoreValue = m_inputType->valueMode() == ValueMode::kValue ||
+                        m_inputType->valueMode() == ValueMode::kFilename;
 
   // https://html.spec.whatwg.org/multipage/forms.html#input-type-change
   //
@@ -868,7 +871,8 @@
 }
 
 void HTMLInputElement::resetImpl() {
-  if (m_inputType->storesValueSeparateFromAttribute()) {
+  if (m_inputType->valueMode() == ValueMode::kValue ||
+      m_inputType->valueMode() == ValueMode::kFilename) {
     setValue(String());
     setNeedsValidityCheck();
   }
diff --git a/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.cpp b/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.cpp
index 7485a3a8..269a0b1 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.cpp
@@ -81,8 +81,8 @@
   return new LayoutButton(&element());
 }
 
-bool BaseButtonInputType::storesValueSeparateFromAttribute() {
-  return false;
+InputType::ValueMode BaseButtonInputType::valueMode() const {
+  return ValueMode::kDefault;
 }
 
 void BaseButtonInputType::setValue(const String& sanitizedValue,
diff --git a/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.h b/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.h
index 4139d3a..33ea33f 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/BaseButtonInputType.h
@@ -55,7 +55,7 @@
   bool shouldSaveAndRestoreFormControlState() const override;
   void appendToFormData(FormData&) const override;
   LayoutObject* createLayoutObject(const ComputedStyle&) const override;
-  bool storesValueSeparateFromAttribute() override;
+  ValueMode valueMode() const override;
   void setValue(const String&, bool, TextFieldEventBehavior) override;
   bool matchesDefaultPseudoClass() override;
 
diff --git a/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.cpp b/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.cpp
index 5ebffeb..ffea71ee 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.cpp
@@ -102,8 +102,8 @@
   return "on";
 }
 
-bool BaseCheckableInputType::storesValueSeparateFromAttribute() {
-  return false;
+InputType::ValueMode BaseCheckableInputType::valueMode() const {
+  return ValueMode::kDefaultOn;
 }
 
 void BaseCheckableInputType::setValue(const String& sanitizedValue,
diff --git a/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.h b/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.h
index da85922..ea6c406 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/BaseCheckableInputType.h
@@ -61,7 +61,7 @@
   void accessKeyAction(bool sendMouseEvents) final;
   bool matchesDefaultPseudoClass() override;
   String fallbackValue() const final;
-  bool storesValueSeparateFromAttribute() final;
+  ValueMode valueMode() const override;
   void setValue(const String&, bool, TextFieldEventBehavior) final;
   void readingChecked() const final;
   bool isCheckable() final;
diff --git a/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.cpp b/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.cpp
index 75e7a19..4167030b 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.cpp
@@ -59,6 +59,10 @@
   return ChooserOnlyTemporalInputTypeView::create(element(), *this);
 }
 
+InputType::ValueMode BaseTemporalInputType::valueMode() const {
+  return ValueMode::kValue;
+}
+
 double BaseTemporalInputType::valueAsDate() const {
   return valueAsDouble();
 }
diff --git a/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.h b/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.h
index f9f809c..8d45f3fb 100644
--- a/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/BaseTemporalInputType.h
@@ -83,6 +83,7 @@
 
   String badInputText() const override;
   InputTypeView* createView() override;
+  ValueMode valueMode() const override;
   double valueAsDate() const override;
   void setValueAsDate(double, ExceptionState&) const override;
   double valueAsDouble() const override;
diff --git a/third_party/WebKit/Source/core/html/forms/ColorInputType.cpp b/third_party/WebKit/Source/core/html/forms/ColorInputType.cpp
index 90b15ba3..d70aeaf 100644
--- a/third_party/WebKit/Source/core/html/forms/ColorInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/ColorInputType.cpp
@@ -93,6 +93,10 @@
   return this;
 }
 
+InputType::ValueMode ColorInputType::valueMode() const {
+  return ValueMode::kValue;
+}
+
 void ColorInputType::countUsage() {
   countUsageIfVisible(UseCounter::InputTypeColor);
 }
diff --git a/third_party/WebKit/Source/core/html/forms/ColorInputType.h b/third_party/WebKit/Source/core/html/forms/ColorInputType.h
index 96cee6c..1c45676d 100644
--- a/third_party/WebKit/Source/core/html/forms/ColorInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/ColorInputType.h
@@ -63,6 +63,7 @@
  private:
   explicit ColorInputType(HTMLInputElement&);
   InputTypeView* createView() override;
+  ValueMode valueMode() const override;
   void valueAttributeChanged() override;
   void countUsage() override;
   const AtomicString& formControlType() const override;
diff --git a/third_party/WebKit/Source/core/html/forms/FileInputType.cpp b/third_party/WebKit/Source/core/html/forms/FileInputType.cpp
index f234817..0d6b38e 100644
--- a/third_party/WebKit/Source/core/html/forms/FileInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/FileInputType.cpp
@@ -157,6 +157,10 @@
   return new LayoutFileUploadControl(&element());
 }
 
+InputType::ValueMode FileInputType::valueMode() const {
+  return ValueMode::kFilename;
+}
+
 bool FileInputType::canSetStringValue() const {
   return false;
 }
diff --git a/third_party/WebKit/Source/core/html/forms/FileInputType.h b/third_party/WebKit/Source/core/html/forms/FileInputType.h
index bc457045..a5b0c44 100644
--- a/third_party/WebKit/Source/core/html/forms/FileInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/FileInputType.h
@@ -76,6 +76,7 @@
   bool canSetStringValue() const override;
   FileList* files() override;
   void setFiles(FileList*) override;
+  ValueMode valueMode() const override;
   bool canSetValue(const String&) override;
   // Checked first, before internal storage or the value attribute.
   bool getTypeSpecificValue(String&) override;
diff --git a/third_party/WebKit/Source/core/html/forms/HiddenInputType.cpp b/third_party/WebKit/Source/core/html/forms/HiddenInputType.cpp
index 7afda97..d90f2b3d 100644
--- a/third_party/WebKit/Source/core/html/forms/HiddenInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/HiddenInputType.cpp
@@ -87,8 +87,8 @@
   return false;
 }
 
-bool HiddenInputType::storesValueSeparateFromAttribute() {
-  return false;
+InputType::ValueMode HiddenInputType::valueMode() const {
+  return ValueMode::kDefault;
 }
 
 void HiddenInputType::setValue(const String& sanitizedValue,
diff --git a/third_party/WebKit/Source/core/html/forms/HiddenInputType.h b/third_party/WebKit/Source/core/html/forms/HiddenInputType.h
index 50fd3d3..cad77ca66 100644
--- a/third_party/WebKit/Source/core/html/forms/HiddenInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/HiddenInputType.h
@@ -55,7 +55,7 @@
   LayoutObject* createLayoutObject(const ComputedStyle&) const override;
   void accessKeyAction(bool sendMouseEvents) override;
   bool layoutObjectIsNeeded() override;
-  bool storesValueSeparateFromAttribute() override;
+  ValueMode valueMode() const override;
   bool isInteractiveContent() const override { return false; }
   bool shouldRespectHeightAndWidthAttributes() override;
   void setValue(const String&, bool, TextFieldEventBehavior) override;
diff --git a/third_party/WebKit/Source/core/html/forms/InputType.cpp b/third_party/WebKit/Source/core/html/forms/InputType.cpp
index d74e3a0..44c21d79 100644
--- a/third_party/WebKit/Source/core/html/forms/InputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/InputType.cpp
@@ -503,10 +503,6 @@
   return true;
 }
 
-bool InputType::storesValueSeparateFromAttribute() {
-  return true;
-}
-
 bool InputType::shouldDispatchFormControlChangeEvent(String& oldValue,
                                                      String& newValue) {
   return !equalIgnoringNullity(oldValue, newValue);
diff --git a/third_party/WebKit/Source/core/html/forms/InputType.h b/third_party/WebKit/Source/core/html/forms/InputType.h
index 7b6e072..54529a7 100644
--- a/third_party/WebKit/Source/core/html/forms/InputType.h
+++ b/third_party/WebKit/Source/core/html/forms/InputType.h
@@ -95,6 +95,10 @@
   // is called.
   virtual String defaultValue() const;
 
+  // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value
+  enum class ValueMode { kValue, kDefault, kDefaultOn, kFilename };
+  virtual ValueMode valueMode() const = 0;
+
   virtual double valueAsDate() const;
   virtual void setValueAsDate(double, ExceptionState&) const;
   virtual double valueAsDouble() const;
@@ -176,7 +180,6 @@
   virtual bool canSetSuggestedValue();
   virtual bool shouldSendChangeEventAfterCheckedChanged();
   virtual bool canSetValue(const String&);
-  virtual bool storesValueSeparateFromAttribute();
   virtual void setValue(const String&,
                         bool valueChanged,
                         TextFieldEventBehavior);
diff --git a/third_party/WebKit/Source/core/html/forms/RangeInputType.cpp b/third_party/WebKit/Source/core/html/forms/RangeInputType.cpp
index 5511ee7..9eb7f880 100644
--- a/third_party/WebKit/Source/core/html/forms/RangeInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/RangeInputType.cpp
@@ -88,6 +88,10 @@
   return this;
 }
 
+InputType::ValueMode RangeInputType::valueMode() const {
+  return ValueMode::kValue;
+}
+
 void RangeInputType::countUsage() {
   countUsageIfVisible(UseCounter::InputTypeRange);
   if (const ComputedStyle* style = element().computedStyle()) {
diff --git a/third_party/WebKit/Source/core/html/forms/RangeInputType.h b/third_party/WebKit/Source/core/html/forms/RangeInputType.h
index f5a2043b..36cd5d1 100644
--- a/third_party/WebKit/Source/core/html/forms/RangeInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/RangeInputType.h
@@ -50,6 +50,7 @@
  private:
   RangeInputType(HTMLInputElement&);
   InputTypeView* createView() override;
+  ValueMode valueMode() const override;
   void countUsage() override;
   const AtomicString& formControlType() const override;
   double valueAsDouble() const override;
diff --git a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
index 0dda4f82..410a938a 100644
--- a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
+++ b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.cpp
@@ -120,6 +120,10 @@
   return this;
 }
 
+InputType::ValueMode TextFieldInputType::valueMode() const {
+  return ValueMode::kValue;
+}
+
 SpinButtonElement* TextFieldInputType::spinButtonElement() const {
   return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(
       ShadowElementNames::spinButton()));
diff --git a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.h b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.h
index 3a5441e..fe0c91ff 100644
--- a/third_party/WebKit/Source/core/html/forms/TextFieldInputType.h
+++ b/third_party/WebKit/Source/core/html/forms/TextFieldInputType.h
@@ -82,6 +82,7 @@
 
  private:
   InputTypeView* createView() override;
+  ValueMode valueMode() const override;
   bool shouldShowFocusRingOnMouseFocus() const final;
   bool isTextField() const final;
   bool valueMissing(const String&) const override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.h b/third_party/WebKit/Source/core/layout/LayoutBox.h
index 5b7bb32..ee2fb8f 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.h
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.h
@@ -1051,7 +1051,7 @@
     AvoidBreaks    // Preferably avoid breaks. If not possible, examine children
                    // to find possible break points.
   };
-  PaginationBreakability getPaginationBreakability() const;
+  virtual PaginationBreakability getPaginationBreakability() const;
 
   LayoutRect localCaretRect(
       InlineBox*,
diff --git a/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.cpp b/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.cpp
index cf82704..7b0ccd3 100644
--- a/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.cpp
@@ -447,13 +447,12 @@
 }
 
 void LayoutFlexibleBox::repositionLogicalHeightDependentFlexItems(
-    Vector<LineContext>& lineContexts,
-    LayoutObject* childToExclude) {
+    Vector<LineContext>& lineContexts) {
   LayoutUnit crossAxisStartEdge =
       lineContexts.isEmpty() ? LayoutUnit() : lineContexts[0].crossAxisOffset;
   alignFlexLines(lineContexts);
 
-  alignChildren(lineContexts, childToExclude);
+  alignChildren(lineContexts);
 
   if (style()->flexWrap() == FlexWrapReverse)
     flipForWrapReverse(lineContexts, crossAxisStartEdge);
@@ -1061,7 +1060,7 @@
   }
 
   updateLogicalHeight();
-  repositionLogicalHeightDependentFlexItems(lineContexts, childToExclude);
+  repositionLogicalHeightDependentFlexItems(lineContexts);
 }
 
 LayoutUnit LayoutFlexibleBox::autoMarginOffsetInMainAxis(
@@ -2133,8 +2132,7 @@
                                           LayoutSize(LayoutUnit(), delta));
 }
 
-void LayoutFlexibleBox::alignChildren(const Vector<LineContext>& lineContexts,
-                                      LayoutObject* childToExclude) {
+void LayoutFlexibleBox::alignChildren(const Vector<LineContext>& lineContexts) {
   // Keep track of the space between the baseline edge and the after edge of
   // the box for each line.
   Vector<LayoutUnit> minMarginAfterBaselines;
@@ -2149,8 +2147,6 @@
     for (size_t childNumber = 0; childNumber < lineContext.flexItems.size();
          ++childNumber) {
       const FlexItem& flexItem = lineContext.flexItems[childNumber];
-      if (flexItem.box == childToExclude)
-        continue;
       if (flexItem.box->isOutOfFlowPositioned()) {
         continue;
       }
diff --git a/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.h b/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.h
index f8cf55c..0abca82 100644
--- a/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.h
+++ b/third_party/WebKit/Source/core/layout/LayoutFlexibleBox.h
@@ -179,8 +179,7 @@
   bool hasAutoMarginsInCrossAxis(const LayoutBox& child) const;
   bool updateAutoMarginsInCrossAxis(LayoutBox& child,
                                     LayoutUnit availableAlignmentSpace);
-  void repositionLogicalHeightDependentFlexItems(Vector<LineContext>&,
-                                                 LayoutObject* childToExclude);
+  void repositionLogicalHeightDependentFlexItems(Vector<LineContext>&);
   LayoutUnit clientLogicalBottomAfterRepositioning();
 
   LayoutUnit availableAlignmentSpaceForChild(LayoutUnit lineCrossAxisExtent,
@@ -243,7 +242,7 @@
                            LayoutUnit crossAxisOffset,
                            LayoutUnit availableFreeSpace);
   void alignFlexLines(Vector<LineContext>&);
-  void alignChildren(const Vector<LineContext>&, LayoutObject* childToExclude);
+  void alignChildren(const Vector<LineContext>&);
   void applyStretchAlignmentToChild(LayoutBox& child,
                                     LayoutUnit lineCrossAxisExtent);
   void flipForRightToLeftColumn(const Vector<LineContext>& lineContexts);
diff --git a/third_party/WebKit/Source/core/layout/LayoutListMarker.h b/third_party/WebKit/Source/core/layout/LayoutListMarker.h
index 150f0bb..fe87edde 100644
--- a/third_party/WebKit/Source/core/layout/LayoutListMarker.h
+++ b/third_party/WebKit/Source/core/layout/LayoutListMarker.h
@@ -105,6 +105,10 @@
   void styleDidChange(StyleDifference, const ComputedStyle* oldStyle) override;
   bool anonymousHasStylePropagationOverride() override { return true; }
 
+  bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const override {
+    return false;
+  }
+
   String m_text;
   Persistent<StyleImage> m_image;
   LayoutListItem* m_listItem;
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp
index 8215fdb..38214df 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp
@@ -195,6 +195,7 @@
 
   for (LayoutTableCell* cell = firstCell(); cell; cell = cell->nextCell()) {
     SubtreeLayoutScope layouter(*cell);
+    cell->setLogicalTop(logicalTop());
     if (!cell->needsLayout())
       markChildForPaginationRelayoutIfNeeded(*cell, layouter);
     if (cell->needsLayout())
@@ -258,6 +259,19 @@
   return false;
 }
 
+LayoutBox::PaginationBreakability LayoutTableRow::getPaginationBreakability()
+    const {
+  PaginationBreakability breakability =
+      LayoutTableBoxComponent::getPaginationBreakability();
+  if (breakability == AllowAnyBreaks) {
+    // Even if the row allows us to break inside, we will want to prevent that
+    // if we have a header group that wants to appear at the top of each page.
+    if (const LayoutTableSection* header = table()->header())
+      breakability = header->getPaginationBreakability();
+  }
+  return breakability;
+}
+
 void LayoutTableRow::paint(const PaintInfo& paintInfo,
                            const LayoutPoint& paintOffset) const {
   TableRowPainter(*this).paint(paintInfo, paintOffset);
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableRow.h b/third_party/WebKit/Source/core/layout/LayoutTableRow.h
index a8999349..5307990 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTableRow.h
+++ b/third_party/WebKit/Source/core/layout/LayoutTableRow.h
@@ -123,6 +123,8 @@
                    const LayoutPoint& accumulatedOffset,
                    HitTestAction) override;
 
+  PaginationBreakability getPaginationBreakability() const final;
+
   void computeOverflow();
 
   const char* name() const override { return "LayoutTableRow"; }
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp b/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp
index f4681d9..fc9f44e 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp
@@ -943,6 +943,7 @@
   LayoutState state(*this, locationOffset());
 
   const Vector<int>& columnPos = table()->effectiveColumnPositions();
+  LayoutUnit rowLogicalTop;
 
   SubtreeLayoutScope layouter(*this);
   for (unsigned r = 0; r < m_grid.size(); ++r) {
@@ -970,9 +971,18 @@
     }
 
     if (LayoutTableRow* rowLayoutObject = m_grid[r].rowLayoutObject) {
-      if (!rowLayoutObject->needsLayout())
-        markChildForPaginationRelayoutIfNeeded(*rowLayoutObject, layouter);
+      if (state.isPaginated()) {
+        rowLayoutObject->setLogicalTop(rowLogicalTop);
+        if (!rowLayoutObject->needsLayout())
+          markChildForPaginationRelayoutIfNeeded(*rowLayoutObject, layouter);
+      }
       rowLayoutObject->layoutIfNeeded();
+      if (state.isPaginated()) {
+        rowLayoutObject->setLogicalHeight(
+            LayoutUnit(logicalHeightForRow(*rowLayoutObject)));
+        rowLogicalTop = rowLayoutObject->logicalBottom();
+        rowLogicalTop += LayoutUnit(table()->vBorderSpacing());
+      }
     }
   }
 
@@ -1253,15 +1263,7 @@
 int LayoutTableSection::paginationStrutForRow(LayoutTableRow* row,
                                               LayoutUnit logicalOffset) const {
   DCHECK(row);
-  // Even if the row allows us to break-inside, we will want to put a strut on
-  // the row if we have a header group that wants to appear at the top of each
-  // page.
-  bool tableHeaderForcesStrut =
-      table()->header()
-          ? table()->header()->getPaginationBreakability() != AllowAnyBreaks
-          : false;
-  if (row->getPaginationBreakability() == AllowAnyBreaks &&
-      !tableHeaderForcesStrut)
+  if (row->getPaginationBreakability() == AllowAnyBreaks)
     return 0;
   LayoutUnit pageLogicalHeight = pageLogicalHeightForOffset(logicalOffset);
   if (!pageLogicalHeight)
@@ -1975,6 +1977,27 @@
   }
 }
 
+int LayoutTableSection::logicalHeightForRow(
+    const LayoutTableRow& rowObject) const {
+  unsigned rowIndex = rowObject.rowIndex();
+  int logicalHeight = 0;
+  const Row& row = m_grid[rowIndex].row;
+  unsigned cols = row.size();
+  for (unsigned colIndex = 0; colIndex < cols; colIndex++) {
+    const CellStruct& cellStruct = cellAt(rowIndex, colIndex);
+    const LayoutTableCell* cell = cellStruct.primaryCell();
+    if (!cell || cellStruct.inColSpan)
+      continue;
+    // TODO(mstensho): Rowspanned cells also need to contribute to row heights
+    // during the first layout pass, in order to get fragmentation right.
+    if (cell->rowSpan() == 1) {
+      logicalHeight =
+          std::max(logicalHeight, cell->logicalHeightForRowSizing());
+    }
+  }
+  return logicalHeight;
+}
+
 bool LayoutTableSection::isRepeatingHeaderGroup() const {
   if (getPaginationBreakability() == LayoutBox::AllowAnyBreaks)
     return false;
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableSection.h b/third_party/WebKit/Source/core/layout/LayoutTableSection.h
index 650af9f..dd91b95c1 100644
--- a/third_party/WebKit/Source/core/layout/LayoutTableSection.h
+++ b/third_party/WebKit/Source/core/layout/LayoutTableSection.h
@@ -419,6 +419,8 @@
 
   void relayoutCellIfFlexed(LayoutTableCell&, int rowIndex, int rowHeight);
 
+  int logicalHeightForRow(const LayoutTableRow&) const;
+
   // The representation of the rows and their cells (CellStruct).
   Vector<RowStruct> m_grid;
 
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
index ac0af34d..83dc704 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
@@ -24,6 +24,41 @@
                   curr_margin_strut.negative_margin_block_start.abs());
 }
 
+// Whether an in-flow block-level child creates a new formatting context.
+//
+// This will *NOT* check the following cases:
+//  - The child is out-of-flow, e.g. floating or abs-pos.
+//  - The child is a inline-level, e.g. "display: inline-block".
+//  - The child establishes a new formatting context, but should be a child of
+//    another layout algorithm, e.g. "display: table-caption" or flex-item.
+bool IsNewFormattingContextForInFlowBlockLevelChild(
+    const NGConstraintSpace& space,
+    const ComputedStyle& style) {
+  // TODO(layout-dev): This doesn't capture a few cases which can't be computed
+  // directly from style yet:
+  //  - The child is a <fieldset>.
+  //  - "column-span: all" is set on the child (requires knowledge that we are
+  //    in a multi-col formatting context).
+  //    (https://drafts.csswg.org/css-multicol-1/#valdef-column-span-all)
+
+  if (style.specifiesColumns() || style.containsPaint() ||
+      style.containsLayout())
+    return true;
+
+  if (!style.isOverflowVisible())
+    return true;
+
+  EDisplay display = style.display();
+  if (display == EDisplay::Grid || display == EDisplay::Flex ||
+      display == EDisplay::Box)
+    return true;
+
+  if (space.WritingMode() != FromPlatformWritingMode(style.getWritingMode()))
+    return true;
+
+  return false;
+}
+
 }  // namespace
 
 NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm(
@@ -73,6 +108,10 @@
     }
     case kStateChildLayout: {
       if (current_child_) {
+        constraint_space_for_children_->SetIsNewFormattingContext(
+            IsNewFormattingContextForInFlowBlockLevelChild(
+                *constraint_space, *current_child_->Style()));
+
         NGFragment* fragment;
         if (!current_child_->Layout(constraint_space_for_children_, &fragment))
           return false;
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm_test.cc b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm_test.cc
index 3e5ac59..35c6bb4 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm_test.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm_test.cc
@@ -163,7 +163,8 @@
 
   EXPECT_TRUE(frag->MarginStrut().IsEmpty());
   ASSERT_EQ(frag->Children().size(), 1UL);
-  const NGPhysicalFragmentBase* div2_fragment = frag->Children()[0];
+  const NGPhysicalFragment* div2_fragment =
+      static_cast<const NGPhysicalFragment*>(frag->Children()[0].get());
   EXPECT_EQ(NGMarginStrut({LayoutUnit(kDiv2MarginTop)}),
             div2_fragment->MarginStrut());
   EXPECT_EQ(kDiv1MarginTop, div2_fragment->TopOffset());
@@ -339,7 +340,8 @@
   ASSERT_EQ(frag->Children().size(), 1UL);
 
   EXPECT_EQ(NGMarginStrut({LayoutUnit(kDiv2Margin), LayoutUnit(kDiv2Margin)}),
-            frag->Children()[0]->MarginStrut());
+            static_cast<const NGPhysicalFragment*>(frag->Children()[0].get())
+                ->MarginStrut());
 
   // Reset padding and verify that margins DO collapse.
   div1_style->setPaddingTop(Length(0, Fixed));
@@ -394,6 +396,67 @@
   EXPECT_EQ(kVerticalDivWidth + kHorizontalDivMarginLeft, child->LeftOffset());
 }
 
+// Verifies that the margin strut of a child with a different writing mode does
+// not get used in the collapsing margins calculation.
+//
+// Test case's HTML representation:
+//   <style>
+//     #div1 { margin-bottom: 10px; height: 60px; writing-mode: vertical-rl; }
+//     #div2 { margin-left: -20px; width: 10px; }
+//     #div3 { margin-top: 40px; height: 60px; }
+//   </style>
+//   <div id="div1">
+//      <div id="div2">vertical</div>
+//   </div>
+//   <div id="div3"></div>
+TEST_F(NGBlockLayoutAlgorithmTest, CollapsingMarginsCase6) {
+  const int kHeight = 60;
+  const int kWidth = 10;
+  const int kMarginBottom = 10;
+  const int kMarginLeft = -20;
+  const int kMarginTop = 40;
+
+  style_->setWidth(Length(500, Fixed));
+  style_->setHeight(Length(500, Fixed));
+
+  // DIV1
+  RefPtr<ComputedStyle> div1_style = ComputedStyle::create();
+  div1_style->setWidth(Length(kWidth, Fixed));
+  div1_style->setHeight(Length(kHeight, Fixed));
+  div1_style->setWritingMode(RightToLeftWritingMode);
+  div1_style->setMarginBottom(Length(kMarginBottom, Fixed));
+  NGBox* div1 = new NGBox(div1_style.get());
+
+  // DIV2
+  RefPtr<ComputedStyle> div2_style = ComputedStyle::create();
+  div2_style->setWidth(Length(kWidth, Fixed));
+  div2_style->setMarginLeft(Length(kMarginLeft, Fixed));
+  NGBox* div2 = new NGBox(div2_style.get());
+
+  // DIV3
+  RefPtr<ComputedStyle> div3_style = ComputedStyle::create();
+  div3_style->setHeight(Length(kHeight, Fixed));
+  div3_style->setMarginTop(Length(kMarginTop, Fixed));
+  NGBox* div3 = new NGBox(div3_style.get());
+
+  div1->SetFirstChild(div2);
+  div1->SetNextSibling(div3);
+
+  auto* space =
+      new NGConstraintSpace(HorizontalTopBottom, LeftToRight,
+                            NGLogicalSize(LayoutUnit(500), LayoutUnit(500)));
+  NGPhysicalFragment* frag = RunBlockLayoutAlgorithm(space, div1);
+
+  ASSERT_EQ(frag->Children().size(), 2UL);
+
+  const NGPhysicalFragmentBase* child1 = frag->Children()[0];
+  EXPECT_EQ(0, child1->TopOffset());
+  EXPECT_EQ(kHeight, child1->Height());
+
+  const NGPhysicalFragmentBase* child2 = frag->Children()[1];
+  EXPECT_EQ(kHeight + std::max(kMarginBottom, kMarginTop), child2->TopOffset());
+}
+
 // Verifies that a box's size includes its borders and padding, and that
 // children are positioned inside the content box.
 //
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_box.cc b/third_party/WebKit/Source/core/layout/ng/ng_box.cc
index e7a4f4e6..cf75f73 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_box.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_box.cc
@@ -15,13 +15,6 @@
 #include "core/layout/ng/ng_writing_mode.h"
 
 namespace blink {
-namespace {
-// TODO(layout-ng): Add m_isNewFc flag to ComputedStyle and set it from
-// StyleResolver instead of getting it calculated by createsNewFormattingContext
-bool IsNewFormattingContext(const LayoutBox* layout_box) {
-  return layout_box && toLayoutBlock(layout_box)->createsNewFormattingContext();
-}
-}  // namespace
 
 NGBox::NGBox(LayoutObject* layout_object)
     : layout_box_(toLayoutBox(layout_object)) {
@@ -46,8 +39,6 @@
     NGConstraintSpace* child_constraint_space = new NGConstraintSpace(
         FromPlatformWritingMode(Style()->getWritingMode()),
         FromPlatformDirection(Style()->direction()), constraint_space);
-    child_constraint_space->SetIsNewFormattingContext(
-        IsNewFormattingContext(layout_box_));
 
     NGPhysicalFragment* fragment = nullptr;
     if (!algorithm_->Layout(child_constraint_space, &fragment))
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.cc b/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.cc
index e2292d2..460f4372f 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.cc
@@ -18,8 +18,7 @@
           container_size.ConvertToPhysical(writing_mode))),
       size_(container_size),
       writing_mode_(writing_mode),
-      direction_(direction),
-      is_new_fc_(false) {}
+      direction_(direction) {}
 
 NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
                                      NGDirection direction,
@@ -27,8 +26,7 @@
     : physical_space_(physical_space),
       size_(physical_space->ContainerSize().ConvertToLogical(writing_mode)),
       writing_mode_(writing_mode),
-      direction_(direction),
-      is_new_fc_(false) {}
+      direction_(direction) {}
 
 NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
                                      NGDirection direction,
@@ -37,8 +35,7 @@
       offset_(constraint_space->Offset()),
       size_(constraint_space->Size()),
       writing_mode_(writing_mode),
-      direction_(direction),
-      is_new_fc_(false) {}
+      direction_(direction) {}
 
 NGConstraintSpace::NGConstraintSpace(const NGConstraintSpace& other,
                                      NGLogicalOffset offset,
@@ -47,17 +44,13 @@
       offset_(offset),
       size_(size),
       writing_mode_(other.WritingMode()),
-      direction_(other.Direction()),
-      is_new_fc_(false) {}
+      direction_(other.Direction()) {}
 
 NGConstraintSpace::NGConstraintSpace(NGWritingMode writing_mode,
                                      NGDirection direction,
                                      const NGConstraintSpace& other,
                                      NGLogicalSize size)
-    : size_(size),
-      writing_mode_(writing_mode),
-      direction_(direction),
-      is_new_fc_(false) {
+    : size_(size), writing_mode_(writing_mode), direction_(direction) {
   physical_space_ =
       new NGPhysicalConstraintSpace(size.ConvertToPhysical(writing_mode));
   for (const auto& exclusion : other.PhysicalSpace()->Exclusions()) {
@@ -67,7 +60,7 @@
 
 NGConstraintSpace* NGConstraintSpace::CreateFromLayoutObject(
     const LayoutBox& box) {
-  bool fixed_inline = false, fixed_block = false;
+  bool fixed_inline = false, fixed_block = false, is_new_fc = false;
   // XXX for orthogonal writing mode this is not right
   LayoutUnit container_logical_width =
       std::max(LayoutUnit(), box.containingBlockLogicalWidthForContent());
@@ -92,6 +85,9 @@
     fixed_block = true;
   }
 
+  if (box.isLayoutBlock() && toLayoutBlock(box).createsNewFormattingContext())
+    is_new_fc = true;
+
   NGConstraintSpace* derived_constraint_space = new NGConstraintSpace(
       FromPlatformWritingMode(box.styleRef().getWritingMode()),
       FromPlatformDirection(box.styleRef().direction()),
@@ -100,6 +96,8 @@
       box.styleRef().overflowInlineDirection() == OverflowAuto,
       box.styleRef().overflowBlockDirection() == OverflowAuto);
   derived_constraint_space->SetFixedSize(fixed_inline, fixed_block);
+  derived_constraint_space->SetIsNewFormattingContext(is_new_fc);
+
   return derived_constraint_space;
 }
 
@@ -108,6 +106,10 @@
       static_cast<NGWritingMode>(writing_mode_));
 }
 
+bool NGConstraintSpace::IsNewFormattingContext() const {
+  return physical_space_->is_new_fc_;
+}
+
 bool NGConstraintSpace::InlineTriggersScrollbar() const {
   return writing_mode_ == HorizontalTopBottom
              ? physical_space_->width_direction_triggers_scrollbar_
@@ -184,6 +186,10 @@
   }
 }
 
+void NGConstraintSpace::SetIsNewFormattingContext(bool is_new_fc) {
+  physical_space_->is_new_fc_ = is_new_fc;
+}
+
 String NGConstraintSpace::ToString() const {
   return String::format("%s,%s %sx%s",
                         offset_.inline_offset.toString().ascii().data(),
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.h b/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.h
index 6454993f..131dbb9 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_constraint_space.h
@@ -87,7 +87,7 @@
 
   // Whether the current constraint space is for the newly established
   // Formatting Context.
-  bool IsNewFormattingContext() const { return is_new_fc_; }
+  bool IsNewFormattingContext() const;
 
   // Whether exceeding the containerSize triggers the presence of a scrollbar
   // for the indicated direction.
@@ -123,9 +123,7 @@
   void SetOverflowTriggersScrollbar(bool inlineTriggers, bool blockTriggers);
   void SetFixedSize(bool inlineFixed, bool blockFixed);
   void SetFragmentationType(NGFragmentationType);
-  // TODO(layout-ng): Add m_isNewFc flag to ComputedStyle and use it instead of
-  // the function below.
-  void SetIsNewFormattingContext(bool is_new_fc) { is_new_fc_ = is_new_fc; }
+  void SetIsNewFormattingContext(bool is_new_fc);
 
   String ToString() const;
 
@@ -135,9 +133,6 @@
   NGLogicalSize size_;
   unsigned writing_mode_ : 3;
   unsigned direction_ : 1;
-  // Whether the current constraint space is for the newly established
-  // formatting Context
-  bool is_new_fc_ : 1;
 };
 
 inline std::ostream& operator<<(std::ostream& stream,
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_fragment.cc b/third_party/WebKit/Source/core/layout/ng/ng_fragment.cc
index 4085e38..ca9dcb63 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_fragment.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_fragment.cc
@@ -4,4 +4,15 @@
 
 #include "core/layout/ng/ng_fragment.h"
 
-namespace blink {}  // namespace blink
+#include "core/layout/ng/ng_physical_fragment.h"
+
+namespace blink {
+
+NGMarginStrut NGFragment::MarginStrut() const {
+  // NOTE: Accessing the margin strut ignoring the writing mode here is fine.
+  // Changing the writing mode establishes a new formatting context, for which
+  // a margin strut is never set for a fragment.
+  return toNGPhysicalFragment(physical_fragment_)->MarginStrut();
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_fragment.h b/third_party/WebKit/Source/core/layout/ng/ng_fragment.h
index 404b3b85..ef2b32d0 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_fragment.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_fragment.h
@@ -23,6 +23,8 @@
              NGDirection direction,
              NGPhysicalFragment* physical_fragment)
       : NGFragmentBase(writing_mode, direction, physical_fragment) {}
+
+  NGMarginStrut MarginStrut() const;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.cc b/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.cc
index 14ea494..0193580f 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.cc
+++ b/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.cc
@@ -41,11 +41,6 @@
              : physical_fragment_->LeftOffset();
 }
 
-NGMarginStrut NGFragmentBase::MarginStrut() const {
-  // TODO(layout-ng): Change MarginStrut to support writing direction.
-  return physical_fragment_->MarginStrut();
-}
-
 DEFINE_TRACE(NGFragmentBase) {
   visitor->trace(physical_fragment_);
 }
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.h b/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.h
index 9e4ada8..48b4948 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_fragment_base.h
@@ -36,8 +36,6 @@
   LayoutUnit InlineOffset() const;
   LayoutUnit BlockOffset() const;
 
-  NGMarginStrut MarginStrut() const;
-
   NGPhysicalFragmentBase* PhysicalFragment() const {
     return physical_fragment_;
   };
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_physical_constraint_space.h b/third_party/WebKit/Source/core/layout/ng/ng_physical_constraint_space.h
index de9b943..b47d5d4 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_physical_constraint_space.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_physical_constraint_space.h
@@ -83,6 +83,10 @@
   unsigned width_direction_fragmentation_type_ : 2;
   unsigned height_direction_fragmentation_type_ : 2;
 
+  // Whether the current constraint space is for the newly established
+  // formatting Context
+  unsigned is_new_fc_ : 1;
+
   HeapVector<Member<const NGExclusion>> exclusions_;
 };
 
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment.h b/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment.h
index 28ad7cb8..4a77cde1 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment.h
@@ -20,7 +20,8 @@
                      NGPhysicalSize overflow,
                      HeapVector<Member<const NGPhysicalFragmentBase>>& children,
                      NGMarginStrut margin_strut)
-      : NGPhysicalFragmentBase(size, overflow, FragmentBox, margin_strut) {
+      : NGPhysicalFragmentBase(size, overflow, FragmentBox),
+        margin_strut_(margin_strut) {
     children_.swap(children);
   }
 
@@ -28,6 +29,8 @@
     return children_;
   }
 
+  NGMarginStrut MarginStrut() const { return margin_strut_; }
+
   DEFINE_INLINE_TRACE_AFTER_DISPATCH() {
     visitor->trace(children_);
     NGPhysicalFragmentBase::traceAfterDispatch(visitor);
@@ -35,10 +38,18 @@
 
  private:
   HeapVector<Member<const NGPhysicalFragmentBase>> children_;
+
+  NGMarginStrut margin_strut_;
 };
 
 WILL_NOT_BE_EAGERLY_TRACED_CLASS(NGPhysicalFragment);
 
+DEFINE_TYPE_CASTS(NGPhysicalFragment,
+                  NGPhysicalFragmentBase,
+                  fragment,
+                  fragment->Type() == NGPhysicalFragmentBase::FragmentBox,
+                  fragment.Type() == NGPhysicalFragmentBase::FragmentBox);
+
 }  // namespace blink
 
 #endif  // NGPhysicalFragment_h
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment_base.h b/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment_base.h
index 0a5000a..75a22999 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment_base.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_physical_fragment_base.h
@@ -59,26 +59,21 @@
     has_been_placed_ = true;
   }
 
-  NGMarginStrut MarginStrut() const { return margin_strut_; }
-
   DEFINE_INLINE_TRACE_AFTER_DISPATCH() {}
   DECLARE_TRACE();
 
  protected:
   NGPhysicalFragmentBase(NGPhysicalSize size,
                          NGPhysicalSize overflow,
-                         NGFragmentType type,
-                         NGMarginStrut margin_strut)
+                         NGFragmentType type)
       : size_(size),
         overflow_(overflow),
-        margin_strut_(margin_strut),
         type_(type),
         has_been_placed_(false) {}
 
   NGPhysicalSize size_;
   NGPhysicalSize overflow_;
   NGPhysicalOffset offset_;
-  NGMarginStrut margin_strut_;
 
   unsigned type_ : 1;
   unsigned has_been_placed_ : 1;
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_physical_text_fragment.h b/third_party/WebKit/Source/core/layout/ng/ng_physical_text_fragment.h
index 5495c46..23d05c3 100644
--- a/third_party/WebKit/Source/core/layout/ng/ng_physical_text_fragment.h
+++ b/third_party/WebKit/Source/core/layout/ng/ng_physical_text_fragment.h
@@ -17,7 +17,7 @@
 class CORE_EXPORT NGPhysicalTextFragment final : public NGPhysicalFragmentBase {
  public:
   NGPhysicalTextFragment(NGPhysicalSize size, NGPhysicalSize overflow)
-      : NGPhysicalFragmentBase(size, overflow, FragmentText, {}) {}
+      : NGPhysicalFragmentBase(size, overflow, FragmentText) {}
 
   String Text() const { return text_list_->Text(start_offset_, end_offset_); }
 
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerClipper.cpp b/third_party/WebKit/Source/core/paint/PaintLayerClipper.cpp
index 9b59bc34a..92ba5a9 100644
--- a/third_party/WebKit/Source/core/paint/PaintLayerClipper.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintLayerClipper.cpp
@@ -441,9 +441,6 @@
   PropertyTreeState propertyTreeState =
       properties->localBorderBoxProperties()->propertyTreeState;
 
-  if (properties->cssClip())
-    propertyTreeState.setClip(properties->cssClip());
-
   if (isForeground && shouldClipOverflow(context) && properties->overflowClip())
     propertyTreeState.setClip(properties->overflowClip());
 
diff --git a/third_party/WebKit/Source/devtools/.gitignore b/third_party/WebKit/Source/devtools/.gitignore
index f9757b2c..05a1d64b 100644
--- a/third_party/WebKit/Source/devtools/.gitignore
+++ b/third_party/WebKit/Source/devtools/.gitignore
@@ -9,5 +9,6 @@
 node_modules
 config.gypi
 npm-debug.log
+/.test_cache
 /release
 /scripts/local_node/runtimes
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn
index 44e01495..0325eb4 100644
--- a/third_party/WebKit/Source/devtools/BUILD.gn
+++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -101,6 +101,7 @@
 ]
 devtools_sdk_js_files = [
   "front_end/sdk/ApplicationCacheModel.js",
+  "front_end/sdk/Connections.js",
   "front_end/sdk/ConsoleModel.js",
   "front_end/sdk/ContentProviders.js",
   "front_end/sdk/CookieParser.js",
@@ -295,7 +296,6 @@
   "front_end/main/remoteDebuggingTerminatedScreen.css",
   "front_end/main/renderingOptions.css",
   "front_end/main/targetCrashedScreen.css",
-  "front_end/main/Connections.js",
   "front_end/main/Main.js",
   "front_end/main/OverlayController.js",
   "front_end/main/RemoteLocationManager.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
index e6ab2af..d4026c0 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
@@ -17,22 +17,44 @@
 WebInspector.Audits2Panel.prototype = {
     _start: function()
     {
-        this._backend().then(backend => backend ? backend.send("start") : undefined).then(console.error.bind(console, "STARTED"));
+        WebInspector.targetManager.interceptMainConnection(this._dispatchProtocolMessage.bind(this)).then(rawConnection => {
+            this._rawConnection = rawConnection;
+            this._send("start");
+        });
+    },
+
+    /**
+     * @param {string} message
+     */
+    _dispatchProtocolMessage: function(message)
+    {
+        this._send("dispatchProtocolMessage", {message: message});
     },
 
     _stop: function()
     {
-        this._backend().then(backend => backend ? backend.send("stop") : undefined).then(console.error.bind(console, "STOPPED"));
+        this._send("stop").then(() => {
+            this._rawConnection.yieldConnection();
+            this._backend.dispose();
+            delete this._backend;
+            delete this._backendPromise;
+        });
     },
 
     /**
-     * @return {!Promise<?WebInspector.ServiceManager.Service>}
+     * @param {string} method
+     * @param {!Object=} params
+     * @return {!Promise<!Object|undefined>}
      */
-    _backend: function()
+    _send: function(method, params)
     {
-        if (!this._backendPromise)
-            this._backendPromise = WebInspector.serviceManager.createAppService("audits2_worker", "Audits2Service", false);
-        return this._backendPromise;
+        if (!this._backendPromise) {
+            this._backendPromise = WebInspector.serviceManager.createAppService("audits2_worker", "Audits2Service", false).then(backend => {
+                this._backend = backend;
+                this._backend.on("sendProtocolMessage", result => this._rawConnection.send(result.message));
+            });
+        }
+        return this._backendPromise.then(() => this._backend ? this._backend.send(method, params) : undefined);
     },
 
     __proto__: WebInspector.Panel.prototype
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js b/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
index 957d7a1..29e0293 100644
--- a/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
+++ b/third_party/WebKit/Source/devtools/front_end/audits2_worker/Audits2Service.js
@@ -6,8 +6,9 @@
  * @constructor
  * @implements {Service}
  */
-function Audits2Service()
+function Audits2Service(notify)
 {
+    this._notify = notify;
 }
 
 Audits2Service.prototype = {
@@ -16,7 +17,10 @@
      */
     start: function()
     {
-        console.error("WORKER START");
+        console.error("************ WORKER START *****************");
+        this._notify("sendProtocolMessage", {message: JSON.stringify({id: 1, method: "Page.enable"})});
+        this._notify("sendProtocolMessage", {message: JSON.stringify({id: 2, method: "Runtime.enable"})});
+        this._notify("sendProtocolMessage", {message: JSON.stringify({id: 3, method: "Page.reload"})});
         return Promise.resolve();
     },
 
@@ -25,16 +29,28 @@
      */
     stop: function()
     {
-        console.error("WORKER STOP");
+        console.error("************ WORKER STOP *****************");
+        return Promise.resolve();
+    },
+
+    /**
+     * @param {!Object=} params
+     * @return {!Promise}
+     */
+    dispatchProtocolMessage: function(params)
+    {
+        console.error("message: " + JSON.stringify(params));
         return Promise.resolve();
     },
 
     /**
      * @override
+     * @return {!Promise}
      */
     dispose: function()
     {
-        console.error("WORKER DISPOSE");
+        console.error("************ WORKER DISPOSE *****************");
+        return Promise.resolve();
     }
 }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/devtools.js b/third_party/WebKit/Source/devtools/front_end/devtools.js
index 1ef19d9..e947c6b 100644
--- a/third_party/WebKit/Source/devtools/front_end/devtools.js
+++ b/third_party/WebKit/Source/devtools/front_end/devtools.js
@@ -691,6 +691,15 @@
 
     /**
      * @override
+     * @param {function()} callback
+     */
+    reattach: function(callback)
+    {
+        DevToolsAPI.sendMessageToEmbedder("reattach", [], callback);
+    },
+
+    /**
+     * @override
      */
     readyForTest: function()
     {
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
index 988fa17e..d7f640d 100644
--- a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
+++ b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
@@ -2918,7 +2918,7 @@
             break;
         case "Enter":
             // Accept any available autocompletions and advance to the next field.
-            if (this.autoCompleteElement && this.autoCompleteElement.textContent.length) {
+            if (this.text() !== this.userEnteredText()) {
                 this.tabKeyPressed();
                 return;
             }
diff --git a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
index 0e5d496..551509c 100644
--- a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
+++ b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHost.js
@@ -391,6 +391,14 @@
 
     /**
      * @override
+     * @param {function()} callback
+     */
+    reattach: function(callback)
+    {
+    },
+
+    /**
+     * @override
      */
     readyForTest: function()
     {
diff --git a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
index cf9cb9e..c9b6f1f0 100644
--- a/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
+++ b/third_party/WebKit/Source/devtools/front_end/host/InspectorFrontendHostAPI.js
@@ -290,6 +290,11 @@
     showContextMenuAtPoint: function(x, y, items, document) { },
 
     /**
+     * @param {function()} callback
+     */
+    reattach: function(callback) { },
+
+    /**
      * @return {boolean}
      */
     isUnderTest: function() { },
diff --git a/third_party/WebKit/Source/devtools/front_end/main/Connections.js b/third_party/WebKit/Source/devtools/front_end/main/Connections.js
deleted file mode 100644
index fbab8df5..0000000
--- a/third_party/WebKit/Source/devtools/front_end/main/Connections.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) 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.
-
-/**
- * @constructor
- * @extends {InspectorBackendClass.Connection}
- */
-WebInspector.MainConnection = function()
-{
-    InspectorBackendClass.Connection.call(this);
-    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
-    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessageChunk, this._dispatchMessageChunk, this);
-}
-
-WebInspector.MainConnection.prototype = {
-    /**
-     * @override
-     * @param {!Object} messageObject
-     */
-    sendMessage: function(messageObject)
-    {
-        var message = JSON.stringify(messageObject);
-        InspectorFrontendHost.sendMessageToBackend(message);
-    },
-
-    /**
-     * @param {!WebInspector.Event} event
-     */
-    _dispatchMessage: function(event)
-    {
-        this.dispatch(/** @type {string} */ (event.data));
-    },
-
-    /**
-     * @param {!WebInspector.Event} event
-     */
-    _dispatchMessageChunk: function(event)
-    {
-        var messageChunk = /** @type {string} */ (event.data["messageChunk"]);
-        var messageSize = /** @type {number} */ (event.data["messageSize"]);
-        if (messageSize) {
-            this._messageBuffer = "";
-            this._messageSize = messageSize;
-        }
-        this._messageBuffer += messageChunk;
-        if (this._messageBuffer.length === this._messageSize) {
-            this.dispatch(this._messageBuffer);
-            this._messageBuffer = "";
-            this._messageSize = 0;
-        }
-    },
-
-    __proto__: InspectorBackendClass.Connection.prototype
-}
-
-/**
- * @constructor
- * @extends {InspectorBackendClass.Connection}
- * @param {string} url
- * @param {function(!InspectorBackendClass.Connection)} onConnectionReady
- */
-WebInspector.WebSocketConnection = function(url, onConnectionReady)
-{
-    InspectorBackendClass.Connection.call(this);
-    this._socket = new WebSocket(url);
-    this._socket.onmessage = this._onMessage.bind(this);
-    this._socket.onerror = this._onError.bind(this);
-    this._socket.onopen = onConnectionReady.bind(null, this);
-    this._socket.onclose = this.connectionClosed.bind(this, "websocket_closed");
-}
-
-/**
- * @param {string} url
- * @param {function(!InspectorBackendClass.Connection)} onConnectionReady
- */
-WebInspector.WebSocketConnection.Create = function(url, onConnectionReady)
-{
-    new WebInspector.WebSocketConnection(url, onConnectionReady);
-}
-
-WebInspector.WebSocketConnection.prototype = {
-
-    /**
-     * @param {!MessageEvent} message
-     */
-    _onMessage: function(message)
-    {
-        var data = /** @type {string} */ (message.data);
-        this.dispatch(data);
-    },
-
-    /**
-     * @param {!Event} error
-     */
-    _onError: function(error)
-    {
-        console.error(error);
-    },
-
-    /**
-     * @override
-     * @param {!Object} messageObject
-     */
-    sendMessage: function(messageObject)
-    {
-        var message = JSON.stringify(messageObject);
-        this._socket.send(message);
-    },
-
-    __proto__: InspectorBackendClass.Connection.prototype
-}
-
-/**
- * @constructor
- * @extends {InspectorBackendClass.Connection}
- */
-WebInspector.StubConnection = function()
-{
-    InspectorBackendClass.Connection.call(this);
-}
-
-WebInspector.StubConnection.prototype = {
-    /**
-     * @override
-     * @param {!Object} messageObject
-     */
-    sendMessage: function(messageObject)
-    {
-        setTimeout(this._respondWithError.bind(this, messageObject), 0);
-    },
-
-    /**
-     * @param {!Object} messageObject
-     */
-    _respondWithError: function(messageObject)
-    {
-        var error = { message: "This is a stub connection, can't dispatch message.", code:  InspectorBackendClass.DevToolsStubErrorCode, data: messageObject };
-        this.dispatch({ id: messageObject.id, error: error });
-    },
-
-    __proto__: InspectorBackendClass.Connection.prototype
-}
diff --git a/third_party/WebKit/Source/devtools/front_end/main/Main.js b/third_party/WebKit/Source/devtools/front_end/main/Main.js
index 88677b8..b9439db8 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/Main.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/Main.js
@@ -30,7 +30,6 @@
 
 /**
  * @constructor
- * @implements {InspectorAgent.Dispatcher}
  * @suppressGlobalPropertiesCheck
  */
 WebInspector.Main = function()
@@ -135,7 +134,7 @@
      */
     _createAppUI: function()
     {
-        console.timeStamp("Main._createApp");
+        console.time("Main._createAppUI");
 
         WebInspector.viewManager = new WebInspector.ViewManager();
 
@@ -210,6 +209,7 @@
         this._registerMessageSinkListener();
 
         self.runtime.extension(WebInspector.AppProvider).instance().then(this._showAppUI.bind(this));
+        console.timeEnd("Main._createAppUI");
     },
 
     /**
@@ -218,10 +218,10 @@
      */
     _showAppUI: function(appProvider)
     {
+        console.time("Main._showAppUI");
         var app = /** @type {!WebInspector.AppProvider} */ (appProvider).createApp();
         // It is important to kick controller lifetime after apps are instantiated.
         WebInspector.dockController.initialize();
-        console.timeStamp("Main._presentUI");
         app.presentUI(document);
 
         var toggleSearchNodeAction = WebInspector.actionRegistry.action("elements.toggle-element-search");
@@ -231,7 +231,6 @@
         WebInspector.inspectorView.createToolbars();
         InspectorFrontendHost.loadCompleted();
 
-        InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.EvaluateForTestInFrontend, this._evaluateForTestInFrontend, this);
         InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ReloadInspectedPage, this._reloadInspectedPage, this);
 
         var extensions = self.runtime.extensions(WebInspector.QueryParamHandler);
@@ -253,43 +252,51 @@
         this._appUIShown = true;
         if (this._fileSystemManagerInitialized) {
             // Allow UI cycles to repaint prior to creating connection.
-            setTimeout(this._createConnection.bind(this), 0);
+            setTimeout(this._connectAndCreateTarget.bind(this), 0);
         }
+        console.timeEnd("Main._showAppUI");
     },
 
     _didInitializeFileSystemManager: function()
     {
         this._fileSystemManagerInitialized = true;
         if (this._appUIShown)
-            this._createConnection();
+            this._connectAndCreateTarget();
     },
 
-    _createConnection: function()
+    _connectAndCreateTarget: function()
     {
-        console.timeStamp("Main._createConnection");
+        console.time("Main._connectAndCreateTarget");
+        this._createConnection().then(connection => {
+            this._createMainTarget(connection);
+            InspectorFrontendHost.readyForTest();
+            WebInspector.targetManager.setMainTargetFactory(this._recreateMainTarget.bind(this));
+            // Asynchronously run the extensions.
+            console.timeEnd("Main._connectAndCreateTarget");
+            setTimeout(this._lateInitialization.bind(this), 100);
+        });
+    },
 
-        if (Runtime.queryParam("ws")) {
-            var ws = "ws://" + Runtime.queryParam("ws");
-            WebInspector.WebSocketConnection.Create(ws, this._connectionEstablished.bind(this));
-            return;
-        }
-
-        if (!InspectorFrontendHost.isHostedMode()) {
-            this._connectionEstablished(new WebInspector.MainConnection());
-            return;
-        }
-
-        this._connectionEstablished(new WebInspector.StubConnection());
+    _recreateMainTarget: function()
+    {
+        this._createConnection().then(this._createMainTarget.bind(this));
     },
 
     /**
-     * @param {!InspectorBackendClass.Connection} connection
+     * @return {!Promise<!InspectorBackendClass.Connection>}
      */
-    _connectionEstablished: function(connection)
+    _createConnection: function()
     {
-        console.timeStamp("Main._connectionEstablished");
-        this._mainConnection = connection;
-        connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, onDisconnected);
+        if (Runtime.queryParam("ws")) {
+            var ws = "ws://" + Runtime.queryParam("ws");
+            return WebInspector.WebSocketConnection.Create(ws).then(connection => {
+                connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, onDisconnected);
+                return connection;
+            });
+        }
+
+        return /** @type {!Promise<!InspectorBackendClass.Connection>} */ (Promise.resolve(InspectorFrontendHost.isHostedMode() ?
+            new WebInspector.StubConnection() : new WebInspector.MainConnection()));
 
         /**
          * @param {!WebInspector.Event} event
@@ -300,32 +307,34 @@
                 return;
             WebInspector.RemoteDebuggingTerminatedScreen.show(event.data.reason);
         }
-
-        this._createMainTarget();
-        InspectorFrontendHost.readyForTest();
-        // Asynchronously run the extensions.
-        setTimeout(this._lateInitialization.bind(this), 100);
     },
 
-    _createMainTarget: function()
+    /**
+     * @param {!InspectorBackendClass.Connection} connection
+     */
+    _createMainTarget: function(connection)
     {
+        console.time("Main._createMainTarget");
         var capabilities =
             WebInspector.Target.Capability.Browser | WebInspector.Target.Capability.DOM |
             WebInspector.Target.Capability.JS | WebInspector.Target.Capability.Log |
             WebInspector.Target.Capability.Network | WebInspector.Target.Capability.Worker;
-        if (Runtime.queryParam("isSharedWorker"))
+        if (Runtime.queryParam("isSharedWorker")) {
             capabilities =
                 WebInspector.Target.Capability.Browser | WebInspector.Target.Capability.Log |
                 WebInspector.Target.Capability.Network | WebInspector.Target.Capability.Worker;
-        else if (Runtime.queryParam("v8only"))
+        } else if (Runtime.queryParam("v8only")) {
             capabilities = WebInspector.Target.Capability.JS;
+        }
 
-        this._mainTarget = WebInspector.targetManager.createTarget(WebInspector.UIString("Main"), capabilities, this._mainConnection, null);
-        this._mainTarget.registerInspectorDispatcher(this);
-        this._mainTarget.runtimeAgent().runIfWaitingForDebugger();
-        if (this._mainTarget.hasBrowserCapability())
-            this._mainTarget.inspectorAgent().enable();
-        console.timeStamp("Main._mainTargetCreated");
+        var target = WebInspector.targetManager.createTarget(WebInspector.UIString("Main"), capabilities, connection, null);
+        target.registerInspectorDispatcher(new WebInspector.Main.InspectorDomainDispatcher(target));
+        target.runtimeAgent().runIfWaitingForDebugger();
+        if (target.hasBrowserCapability())
+            target.inspectorAgent().enable();
+        if (Runtime.experiments.isEnabled("nodeDebugging"))
+            new WebInspector.RemoteLocationManager(target);
+        console.timeEnd("Main._createMainTarget");
     },
 
     _lateInitialization: function()
@@ -333,8 +342,6 @@
         console.timeStamp("Main._lateInitialization");
         this._registerShortcuts();
         WebInspector.extensionServer.initializeExtensions();
-        if (Runtime.experiments.isEnabled("nodeDebugging"))
-            new WebInspector.RemoteLocationManager(this._mainTarget);
     },
 
     _registerForwardedShortcuts: function()
@@ -536,6 +543,24 @@
         WebInspector.Main._reloadPage(hard);
     },
 
+    _onSuspendStateChanged: function()
+    {
+        var suspended = WebInspector.targetManager.allTargetsSuspended();
+        WebInspector.inspectorView.onSuspendStateChanged(suspended);
+    }
+}
+
+/**
+ * @constructor
+ * @implements {InspectorAgent.Dispatcher}
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.Main.InspectorDomainDispatcher = function(target)
+{
+    this._target = target;
+}
+
+WebInspector.Main.InspectorDomainDispatcher.prototype = {
     /**
      * @override
      * @param {string} reason
@@ -551,45 +576,13 @@
      */
     targetCrashed: function()
     {
-        var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._mainTarget);
+        var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._target);
         if (debuggerModel)
             WebInspector.TargetCrashedScreen.show(debuggerModel);
-    },
-
-    _onSuspendStateChanged: function()
-    {
-        var suspended = WebInspector.targetManager.allTargetsSuspended();
-        WebInspector.inspectorView.onSuspendStateChanged(suspended);
-    },
-
-    /**
-     * @param {!WebInspector.Event} event
-     */
-    _evaluateForTestInFrontend: function(event)
-    {
-        if (!InspectorFrontendHost.isUnderTest())
-            return;
-
-        var callId = /** @type {number} */ (event.data["callId"]);
-        var script = /** @type {number} */ (event.data["script"]);
-
-        /**
-         * @suppressGlobalPropertiesCheck
-         */
-        function invokeMethod()
-        {
-            try {
-                script = script + "//# sourceURL=evaluateInWebInspector" + callId + ".js";
-                window.eval(script);
-            } catch (e) {
-                console.error(e.stack);
-            }
-        }
-
-        this._mainConnection.deprecatedRunAfterPendingDispatches(invokeMethod);
     }
 }
 
+
 /**
  * @constructor
  * @implements {WebInspector.ActionDelegate}
diff --git a/third_party/WebKit/Source/devtools/front_end/main/RemoteLocationManager.js b/third_party/WebKit/Source/devtools/front_end/main/RemoteLocationManager.js
index 16c032a9..a613dc3 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/RemoteLocationManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/main/RemoteLocationManager.js
@@ -45,7 +45,7 @@
      * @param {?Protocol.Error} error
      * @return {!Promise}
      */
-    _requestTargets: function (error)
+    _requestTargets: function(error)
     {
         if (error) {
             console.error(error);
@@ -79,7 +79,7 @@
                 var target = this._connectedTargets.get(targetId);
                 this._connectedTargets.delete(targetId);
                 if (target)
-                    WebInspector.targetManager.removeTarget(target);
+                    target.dispose();
             }
         }
 
@@ -140,9 +140,12 @@
         this._agent.sendMessage(this._targetId, JSON.stringify(messageObject));
     },
 
-    _close: function()
+    /**
+     * @override
+     */
+    forceClose: function()
     {
-        this.connectionClosed("node_detached");
+        this._agent.detach(this._targetId, () => {});
     },
 
     __proto__: InspectorBackendClass.Connection.prototype
diff --git a/third_party/WebKit/Source/devtools/front_end/main/module.json b/third_party/WebKit/Source/devtools/front_end/main/module.json
index f1ccfc6..28f81872 100644
--- a/third_party/WebKit/Source/devtools/front_end/main/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/main/module.json
@@ -360,7 +360,6 @@
         "RenderingOptions.js",
         "SimpleApp.js",
         "OverlayController.js",
-        "Connections.js",
         "Main.js",
         "RemoteLocationManager.js"
     ],
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/Connections.js b/third_party/WebKit/Source/devtools/front_end/sdk/Connections.js
new file mode 100644
index 0000000..201c39d
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/Connections.js
@@ -0,0 +1,255 @@
+// Copyright (c) 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.
+
+/**
+ * @constructor
+ * @extends {InspectorBackendClass.Connection}
+ */
+WebInspector.MainConnection = function()
+{
+    InspectorBackendClass.Connection.call(this);
+    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
+    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessageChunk, this._dispatchMessageChunk, this);
+    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.EvaluateForTestInFrontend, this._evaluateForTestInFrontend, this);
+}
+
+WebInspector.MainConnection.prototype = {
+    /**
+     * @override
+     * @param {!Object} messageObject
+     */
+    sendMessage: function(messageObject)
+    {
+        var message = JSON.stringify(messageObject);
+        InspectorFrontendHost.sendMessageToBackend(message);
+    },
+
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _dispatchMessage: function(event)
+    {
+        this.dispatch(/** @type {string} */ (event.data));
+    },
+
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _dispatchMessageChunk: function(event)
+    {
+        var messageChunk = /** @type {string} */ (event.data["messageChunk"]);
+        var messageSize = /** @type {number} */ (event.data["messageSize"]);
+        if (messageSize) {
+            this._messageBuffer = "";
+            this._messageSize = messageSize;
+        }
+        this._messageBuffer += messageChunk;
+        if (this._messageBuffer.length === this._messageSize) {
+            this.dispatch(this._messageBuffer);
+            this._messageBuffer = "";
+            this._messageSize = 0;
+        }
+    },
+
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _evaluateForTestInFrontend: function(event)
+    {
+        if (!InspectorFrontendHost.isUnderTest())
+            return;
+
+        var callId = /** @type {number} */ (event.data["callId"]);
+        var script = /** @type {number} */ (event.data["script"]);
+
+        /**
+         * @suppressGlobalPropertiesCheck
+         */
+        function invokeMethod()
+        {
+            try {
+                script = script + "//# sourceURL=evaluateInWebInspector" + callId + ".js";
+                window.eval(script);
+            } catch (e) {
+                console.error(e.stack);
+            }
+        }
+
+        this.deprecatedRunAfterPendingDispatches(invokeMethod);
+    },
+
+    /**
+     * @override
+     */
+    forceClose: function()
+    {
+        InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
+        InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.DispatchMessageChunk, this._dispatchMessageChunk, this);
+        InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.EvaluateForTestInFrontend, this._evaluateForTestInFrontend, this);
+    },
+
+    __proto__: InspectorBackendClass.Connection.prototype
+}
+
+/**
+ * @constructor
+ * @extends {InspectorBackendClass.Connection}
+ * @param {string} url
+ * @param {function(!InspectorBackendClass.Connection)} onConnectionReady
+ */
+WebInspector.WebSocketConnection = function(url, onConnectionReady)
+{
+    InspectorBackendClass.Connection.call(this);
+    this._socket = new WebSocket(url);
+    this._socket.onmessage = this._onMessage.bind(this);
+    this._socket.onerror = this._onError.bind(this);
+    this._socket.onopen = onConnectionReady.bind(null, this);
+    this._socket.onclose = this.connectionClosed.bind(this, "websocket_closed");
+}
+
+/**
+ * @param {string} url
+ * @return {!Promise<!InspectorBackendClass.Connection>}
+ */
+WebInspector.WebSocketConnection.Create = function(url)
+{
+    var fulfill;
+    var result = new Promise(resolve => fulfill = resolve);
+    new WebInspector.WebSocketConnection(url, fulfill);
+    return result;
+}
+
+WebInspector.WebSocketConnection.prototype = {
+
+    /**
+     * @param {!MessageEvent} message
+     */
+    _onMessage: function(message)
+    {
+        var data = /** @type {string} */ (message.data);
+        this.dispatch(data);
+    },
+
+    /**
+     * @param {!Event} error
+     */
+    _onError: function(error)
+    {
+        console.error(error);
+    },
+
+    /**
+     * @override
+     */
+    forceClose: function()
+    {
+        this._socket.close();
+    },
+
+    /**
+     * @override
+     * @param {!Object} messageObject
+     */
+    sendMessage: function(messageObject)
+    {
+        var message = JSON.stringify(messageObject);
+        this._socket.send(message);
+    },
+
+    __proto__: InspectorBackendClass.Connection.prototype
+}
+
+/**
+ * @constructor
+ * @extends {InspectorBackendClass.Connection}
+ */
+WebInspector.StubConnection = function()
+{
+    InspectorBackendClass.Connection.call(this);
+}
+
+WebInspector.StubConnection.prototype = {
+    /**
+     * @override
+     * @param {!Object} messageObject
+     */
+    sendMessage: function(messageObject)
+    {
+        setTimeout(this._respondWithError.bind(this, messageObject), 0);
+    },
+
+    /**
+     * @param {!Object} messageObject
+     */
+    _respondWithError: function(messageObject)
+    {
+        var error = { message: "This is a stub connection, can't dispatch message.", code:  InspectorBackendClass.DevToolsStubErrorCode, data: messageObject };
+        this.dispatch({ id: messageObject.id, error: error });
+    },
+
+    __proto__: InspectorBackendClass.Connection.prototype
+}
+
+
+/**
+ * @constructor
+ * @param {function(string)} dispatchCallback
+ * @param {function()} yieldCallback
+ */
+WebInspector.RawProtocolConnection = function(dispatchCallback, yieldCallback)
+{
+    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
+    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.DispatchMessageChunk, this._dispatchMessageChunk, this);
+    this._dispatchCallback = dispatchCallback;
+    this._yieldCallback = yieldCallback;
+    this._isClosed = false;
+}
+
+WebInspector.RawProtocolConnection.prototype = {
+    /**
+     * @param {string} message
+     */
+    send: function(message)
+    {
+        if (this._isClosed)
+            return;
+        InspectorFrontendHost.sendMessageToBackend(message);
+    },
+
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _dispatchMessage: function(event)
+    {
+        this._dispatchCallback(/** @type {string} */ (event.data));
+    },
+
+    /**
+     * @param {!WebInspector.Event} event
+     */
+    _dispatchMessageChunk: function(event)
+    {
+        var messageChunk = /** @type {string} */ (event.data["messageChunk"]);
+        var messageSize = /** @type {number} */ (event.data["messageSize"]);
+        if (messageSize) {
+            this._messageBuffer = "";
+            this._messageSize = messageSize;
+        }
+        this._messageBuffer += messageChunk;
+        if (this._messageBuffer.length === this._messageSize) {
+            this._dispatchCallback(this._messageBuffer);
+            this._messageBuffer = "";
+            this._messageSize = 0;
+        }
+    },
+
+    yieldConnection: function()
+    {
+        InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.DispatchMessage, this._dispatchMessage, this);
+        InspectorFrontendHost.events.removeEventListener(InspectorFrontendHostAPI.Events.DispatchMessageChunk, this._dispatchMessageChunk, this);
+        this._isClosed = true;
+        delete this._dispatchCallback;
+        this._yieldCallback();
+    }
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/InspectorBackend.js b/third_party/WebKit/Source/devtools/front_end/sdk/InspectorBackend.js
index b81fb233..cd0d3c1 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/InspectorBackend.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/InspectorBackend.js
@@ -420,6 +420,19 @@
         console.log(message);
     },
 
+    close: function()
+    {
+        this.forceClose();
+        this.connectionClosed("force close");
+    },
+
+    /**
+     * @protected
+     */
+    forceClose: function()
+    {
+    },
+
     /**
      * @protected
      * @param {string} reason
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
index 69f79f98..103613e 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/SubTargetsManager.js
@@ -65,7 +65,7 @@
     dispose: function()
     {
         for (var connection of this._connections.values())
-            connection._close();
+            connection.close();
         this._connections.clear();
         this._attachedTargets.clear();
     },
@@ -194,7 +194,7 @@
     {
         var connection = this._connections.get(targetId);
         if (connection)
-            connection._close();
+            connection._reportClosed();
         this._connections.delete(targetId);
         var target = this._attachedTargets.get(targetId);
         this._attachedTargets.delete(targetId);
@@ -297,7 +297,15 @@
         this._agent.sendMessageToTarget(this._targetId, JSON.stringify(messageObject));
     },
 
-    _close: function()
+    /**
+     * @override
+     */
+    forceClose: function()
+    {
+        this._agent.detachFromTarget(this._targetId);
+    },
+
+    _reportClosed: function()
     {
         this.connectionClosed("target_terminated");
     },
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/Target.js b/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
index f6b0db1..9db628e 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/Target.js
@@ -22,7 +22,7 @@
     this._capabilitiesMask = capabilitiesMask;
     this._connection = connection;
     this._parentTarget = parentTarget;
-    connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, this._onDisconnect, this);
+    connection.addEventListener(InspectorBackendClass.Connection.Events.Disconnected, this.dispose, this);
     this._id = WebInspector.Target._nextId++;
 
     /** @type {!Map.<!Function, !WebInspector.SDKModel>} */
@@ -78,7 +78,6 @@
     },
 
     /**
-     *
      * @return {!InspectorBackendClass.Connection}
      */
     connection: function()
@@ -161,15 +160,11 @@
         return this._parentTarget;
     },
 
-    _onDisconnect: function()
+    dispose: function()
     {
         this._targetManager.removeTarget(this);
-        this._dispose();
-    },
-
-    _dispose: function()
-    {
-        this._targetManager.dispatchEventToListeners(WebInspector.TargetManager.Events.TargetDisposed, this);
+        for (var model of this._modelByConstructor.valuesArray())
+            model.dispose();
         if (this.workerManager)
             this.workerManager.dispose();
     },
@@ -258,7 +253,6 @@
 {
     WebInspector.SDKObject.call(this, target);
     target._modelByConstructor.set(modelClass, this);
-    WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.TargetDisposed, this._targetDisposed, this);
 }
 
 WebInspector.SDKModel.prototype = {
@@ -289,7 +283,6 @@
         if (target !== this._target)
             return;
         this.dispose();
-        WebInspector.targetManager.removeEventListener(WebInspector.TargetManager.Events.TargetDisposed, this._targetDisposed, this);
     },
 
     __proto__: WebInspector.SDKObject.prototype
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
index a98d55b..1ff7ec9 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/TargetManager.js
@@ -236,6 +236,38 @@
     },
 
     /**
+     * @param {function()} factory
+     */
+    setMainTargetFactory: function(factory)
+    {
+        this._mainTargetFactory = factory;
+    },
+
+    /**
+     * @param {function(string)} dispatch
+     * @return {!Promise<!WebInspector.RawProtocolConnection>}
+     */
+    interceptMainConnection: function(dispatch)
+    {
+        var target = WebInspector.targetManager.mainTarget();
+        if (target)
+            target.connection().close();
+
+        var fulfill;
+        var result = new Promise(resolve => fulfill = resolve);
+        InspectorFrontendHost.reattach(() => fulfill(new WebInspector.RawProtocolConnection(dispatch, yieldCallback.bind(this))));
+        return result;
+
+        /**
+         * @this {WebInspector.TargetManager}
+         */
+        function yieldCallback()
+        {
+            InspectorFrontendHost.reattach(this._mainTargetFactory());
+        }
+    },
+
+    /**
      * @param {!WebInspector.Target} target
      * @return {!Array<!WebInspector.TargetManager.Observer>}
      */
@@ -289,6 +321,8 @@
      */
     removeTarget: function(target)
     {
+        if (!this._targets.includes(target))
+            return;
         this._targets.remove(target);
         var resourceTreeModel = WebInspector.ResourceTreeModel.fromTarget(target);
         var treeModelListeners = resourceTreeModel && resourceTreeModel[WebInspector.TargetManager._listenersSymbol];
@@ -309,14 +343,6 @@
         }
     },
 
-    removeAllTargets: function()
-    {
-        var targets = this._targets.slice();
-        for (var i = targets.length - 1; i >=0 ; --i)
-            this.removeTarget(targets[i]);
-        this._targets = [];
-    },
-
     /**
      * @param {number=} capabilitiesMask
      * @return {!Array.<!WebInspector.Target>}
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/WorkerManager.js b/third_party/WebKit/Source/devtools/front_end/sdk/WorkerManager.js
index 1dd507d..1969ff6a 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/WorkerManager.js
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/WorkerManager.js
@@ -78,7 +78,7 @@
     _reset: function()
     {
         for (var connection of this._connections.values())
-            connection._close();
+            connection.close();
         this._connections.clear();
         this._targetsByWorkerId.clear();
     },
@@ -119,7 +119,7 @@
     {
         var connection = this._connections.get(workerId);
         if (connection)
-            connection._close();
+            connection._reportClosed();
         this._connections.delete(workerId);
         this._targetsByWorkerId.delete(workerId);
     },
@@ -223,7 +223,7 @@
         this._agent.sendMessageToWorker(this._workerId, JSON.stringify(messageObject));
     },
 
-    _close: function()
+    _reportClosed: function()
     {
         this.connectionClosed("worker_terminated");
     },
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/module.json b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
index c7eb080..8eaba34 100644
--- a/third_party/WebKit/Source/devtools/front_end/sdk/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/sdk/module.json
@@ -77,6 +77,7 @@
         "Target.js",
         "TargetManager.js",
         "ApplicationCacheModel.js",
+        "Connections.js",
         "ConsoleModel.js",
         "ContentProviders.js",
         "CookieParser.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js b/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
index eb4a185..8ec159f 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/TextPrompt.js
@@ -170,8 +170,8 @@
     userEnteredText: function()
     {
         var text = this.text();
-        if (this.autoCompleteElement) {
-            var addition = this.autoCompleteElement.textContent;
+        if (this._autocompleteElement) {
+            var addition = this._autocompleteElement.textContent;
             text = text.substring(0, text.length - addition.length);
         }
         return text;
@@ -386,11 +386,11 @@
             delete this._completeTimeout;
         }
 
-        if (!this.autoCompleteElement)
+        if (!this._autocompleteElement)
             return;
 
-        this.autoCompleteElement.remove();
-        delete this.autoCompleteElement;
+        this._autocompleteElement.remove();
+        delete this._autocompleteElement;
         delete this._userEnteredRange;
         delete this._userEnteredText;
     },
@@ -565,10 +565,10 @@
             var prefixTextNode = createTextNode(prefixText);
             fullWordRange.insertNode(prefixTextNode);
 
-            this.autoCompleteElement = createElementWithClass("span", "auto-complete-text");
-            this.autoCompleteElement.textContent = suffixText;
+            this._autocompleteElement = createElementWithClass("span", "auto-complete-text");
+            this._autocompleteElement.textContent = suffixText;
 
-            prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
+            prefixTextNode.parentNode.insertBefore(this._autocompleteElement, prefixTextNode.nextSibling);
 
             finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
             finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
@@ -580,7 +580,7 @@
 
     _completeCommonPrefix: function()
     {
-        if (!this.autoCompleteElement || !this._commonPrefix || !this._userEnteredText || !this._commonPrefix.startsWith(this._userEnteredText))
+        if (!this._autocompleteElement || !this._commonPrefix || !this._userEnteredText || !this._commonPrefix.startsWith(this._userEnteredText))
             return;
 
         if (!this.isSuggestBoxVisible()) {
@@ -588,7 +588,7 @@
             return;
         }
 
-        this.autoCompleteElement.textContent = this._commonPrefix.substring(this._userEnteredText.length);
+        this._autocompleteElement.textContent = this._commonPrefix.substring(this._userEnteredText.length);
         this._acceptSuggestionInternal(true);
     },
 
@@ -620,9 +620,9 @@
         var finalSelectionRange = this._createRange();
         var completionTextNode = createTextNode(completionText);
         this._userEnteredRange.insertNode(completionTextNode);
-        if (this.autoCompleteElement) {
-            this.autoCompleteElement.remove();
-            delete this.autoCompleteElement;
+        if (this._autocompleteElement) {
+            this._autocompleteElement.remove();
+            delete this._autocompleteElement;
         }
 
         if (isIntermediateSuggestion)
@@ -653,13 +653,13 @@
      */
     _acceptSuggestionInternal: function(prefixAccepted)
     {
-        if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
+        if (!this._autocompleteElement || !this._autocompleteElement.parentNode)
             return false;
 
-        var text = this.autoCompleteElement.textContent;
+        var text = this._autocompleteElement.textContent;
         var textNode = createTextNode(text);
-        this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
-        delete this.autoCompleteElement;
+        this._autocompleteElement.parentNode.replaceChild(textNode, this._autocompleteElement);
+        delete this._autocompleteElement;
 
         var finalSelectionRange = this._createRange();
         finalSelectionRange.setStart(textNode, text.length);
@@ -719,7 +719,7 @@
         var foundNextText = false;
         while (node) {
             if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
-                if (foundNextText && (!this.autoCompleteElement || !this.autoCompleteElement.isAncestor(node)))
+                if (foundNextText && (!this._autocompleteElement || !this._autocompleteElement.isAncestor(node)))
                     return false;
                 foundNextText = true;
             }
diff --git a/third_party/WebKit/Source/devtools/front_end/worker_service/ServiceDispatcher.js b/third_party/WebKit/Source/devtools/front_end/worker_service/ServiceDispatcher.js
index fe4ff44..a9e36f3 100644
--- a/third_party/WebKit/Source/devtools/front_end/worker_service/ServiceDispatcher.js
+++ b/third_party/WebKit/Source/devtools/front_end/worker_service/ServiceDispatcher.js
@@ -8,6 +8,9 @@
 function Service() { }
 
 Service.prototype = {
+    /**
+     * @return {!Promise}
+     */
     dispose: function() { }
 }
 
@@ -46,7 +49,7 @@
                 return;
             }
             this._dispatchMessage(message);
-        } catch(e) {
+        } catch (e) {
             this._sendErrorResponse(message["id"], e.toString());
         }
     },
diff --git a/third_party/WebKit/Source/devtools/package.json b/third_party/WebKit/Source/devtools/package.json
index 1383f86..23600f0 100644
--- a/third_party/WebKit/Source/devtools/package.json
+++ b/third_party/WebKit/Source/devtools/package.json
@@ -5,7 +5,9 @@
     "start": "node scripts/start_chrome_and_server.js",
     "chrome": "node scripts/chrome_debug_launcher/launch_chrome.js",
     "server": "node scripts/hosted_mode/server.js",
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "test": "node scripts/npm_test.js",
+    "test:build": "node scripts/npm_test.js --build-only",
+    "test:run": "node scripts/npm_test.js --test-only",
     "lint": "eslint -c front_end/.eslintrc.js --ignore-path front_end/.eslintignore front_end"
   },
   "repository": {
diff --git a/third_party/WebKit/Source/devtools/scripts/npm_test.js b/third_party/WebKit/Source/devtools/scripts/npm_test.js
new file mode 100644
index 0000000..dc14020
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/scripts/npm_test.js
@@ -0,0 +1,263 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var childProcess = require("child_process");
+var fs = require("fs");
+var path = require("path");
+var shell = require("child_process").execSync;
+
+var utils = require("./utils");
+
+var Flags = {
+    BUILD_ONLY: "--build-only",
+    DEBUG_DEVTOOLS: "--debug-devtools",
+    TEST_ONLY: "--test-only",
+};
+
+var IS_DEBUG_ENABLED = utils.includes(process.argv, Flags.DEBUG_DEVTOOLS);
+var IS_BUILD_ONLY = utils.includes(process.argv, Flags.BUILD_ONLY);
+var IS_TEST_ONLY = utils.includes(process.argv, Flags.TEST_ONLY);
+
+var CONTENT_SHELL_ZIP = "content-shell.zip";
+var MAX_CONTENT_SHELLS = 10;
+var PLATFORM = getPlatform();
+var PYTHON = process.platform === "win32" ? "python.bat" : "python";
+
+var BLINK_TEST_PATH = path.resolve(__dirname, "..", "..", "..", "..", "..", "blink", "tools", "run_layout_tests.py");
+var CACHE_PATH = path.resolve(__dirname, "..", ".test_cache");
+var SOURCE_PATH = path.resolve(__dirname, "..", "front_end");
+
+function main(){
+    if (IS_TEST_ONLY) {
+        findPreviousUploadedPosition(findMostRecentChromiumCommit())
+            .then(commitPosition => runTests(path.resolve(CACHE_PATH, commitPosition, "out")));
+        return;
+    }
+    if (!utils.isDir(CACHE_PATH))
+        fs.mkdirSync(CACHE_PATH);
+    deleteOldContentShells();
+    findPreviousUploadedPosition(findMostRecentChromiumCommit())
+        .then(onUploadedCommitPosition)
+        .catch(onError);
+
+    function onError(error) {
+        console.log("Unable to run tests because of error:", error);
+        console.log(`Try removing the .test_cache folder [${CACHE_PATH}] and retrying`);
+    }
+}
+main();
+
+function onUploadedCommitPosition(commitPosition)
+{
+    var contentShellDirPath = path.resolve(CACHE_PATH, commitPosition, "out", "Release");
+    var hasCachedContentShell = utils.isFile(getContentShellBinaryPath(contentShellDirPath));
+    if (hasCachedContentShell) {
+        var contentShellPath = path.resolve(CACHE_PATH, commitPosition, "out");
+        console.log(`Using cached content shell at: ${contentShellPath}`);
+        return buildAndTest(contentShellPath);
+    }
+    return prepareContentShellDirectory(commitPosition)
+        .then(downloadContentShell)
+        .then(extractContentShell)
+        .then(buildAndTest);
+}
+
+function getPlatform()
+{
+    if (process.platform === "linux") {
+        if (process.arch === "x64")
+            return "Linux_x64";
+        throw new Error("Pre-compiled content shells are only available for x64 on Linux");
+    }
+    if (process.platform === "win32") {
+        if (process.arch === "x64")
+            return "Win_x64";
+        return "Win";
+    }
+    if (process.platform === "darwin") {
+        return "Mac";
+    }
+    throw new Error(`Unrecognized platform detected: ${process.platform}`);
+}
+
+function findMostRecentChromiumCommit()
+{
+    var commitMessage = shell(`git log --max-count=1 --grep="Cr-Commit-Position"`).toString().trim();
+    var commitPosition = commitMessage.match(/Cr-Commit-Position: refs\/heads\/master@\{#([0-9]+)\}/)[1];
+    return commitPosition;
+}
+
+function deleteOldContentShells()
+{
+    var files = fs.readdirSync(CACHE_PATH);
+    if (files.length < MAX_CONTENT_SHELLS)
+        return;
+    files.sort((a, b) => parseInt(b, 10) - parseInt(a, 10));
+    var remainingNumberOfContentShells = MAX_CONTENT_SHELLS / 2;
+    var oldContentShellDirs = files.slice(remainingNumberOfContentShells);
+    for (var i = 0; i < oldContentShellDirs.length; i++)
+        utils.removeRecursive(path.resolve(CACHE_PATH, oldContentShellDirs[i]));
+    console.log(`Removed old content shells: ${oldContentShellDirs}`)
+}
+
+function findPreviousUploadedPosition(commitPosition)
+{
+    var previousPosition = commitPosition - 100;
+    var positionsListURL = `http://commondatastorage.googleapis.com/chromium-browser-snapshots/?delimiter=/&prefix=${PLATFORM}/&marker=${PLATFORM}/${previousPosition}/`;
+    return utils.fetch(positionsListURL)
+        .then(onPositionsList)
+        .catch(onError);
+
+    function onPositionsList(buffer)
+    {
+        var positions = buffer.toString("binary")
+            .match(/([^<>]+)(?=<\/Prefix><\/CommonPrefixes>)/g)
+            .map(prefixedPosition => prefixedPosition.split("/")[1])
+            .map(positionString => parseInt(positionString, 10));
+        var positionSet = new Set(positions);
+        var previousUploadedPosition = commitPosition;
+        while (commitPosition - previousUploadedPosition < 100) {
+            if (positionSet.has(previousUploadedPosition))
+                return previousUploadedPosition.toString();
+            previousUploadedPosition--;
+        }
+        onError();
+    }
+
+    function onError(error)
+    {
+        if (error)
+            console.log(`Received error: ${error} trying to fetch positions list from url: ${positionsListURL}`);
+        throw new Error(`Unable to find a previous upload position for commit position: ${commitPosition}`);
+    }
+}
+
+function prepareContentShellDirectory(contentShellCommitPosition)
+{
+    var contentShellPath = path.join(CACHE_PATH, contentShellCommitPosition);
+    if (utils.isDir(contentShellPath))
+        utils.removeRecursive(contentShellPath);
+    fs.mkdirSync(contentShellPath);
+    return Promise.resolve(contentShellCommitPosition);
+}
+
+function downloadContentShell(commitPosition)
+{
+    var url = `http://commondatastorage.googleapis.com/chromium-browser-snapshots/${PLATFORM}/${commitPosition}/${CONTENT_SHELL_ZIP}`;
+    console.log("Downloading content shell from:", url);
+    console.log("NOTE: Download is ~35-65 MB depending on OS");
+    return utils.fetch(url)
+        .then(writeZip)
+        .catch(onError);
+
+    function writeZip(buffer)
+    {
+        console.log("Completed download of content shell");
+        var contentShellZipPath = path.join(CACHE_PATH, commitPosition, CONTENT_SHELL_ZIP);
+        fs.writeFileSync(contentShellZipPath, buffer);
+        return contentShellZipPath;
+    }
+
+    function onError(error)
+    {
+        console.log(`Received error: ${error} trying to download content shell from url: ${url}`);
+        throw new Error("Unable to download content shell");
+    }
+}
+
+function extractContentShell(contentShellZipPath)
+{
+    console.log(`Extracting content shell zip: ${contentShellZipPath}`);
+    var unzipScriptPath = path.resolve(__dirname, "unzip.py");
+    var src = contentShellZipPath;
+    var dest = path.resolve(path.dirname(src), "out");
+    shell(`${PYTHON} ${unzipScriptPath} ${src} ${dest}`);
+    fs.unlinkSync(src);
+    var originalDirPath = path.resolve(dest, "content-shell");
+    var newDirPath = path.resolve(dest, "Release");
+    fs.renameSync(originalDirPath, newDirPath);
+    fs.chmodSync(getContentShellBinaryPath(newDirPath), "755");
+    if (process.platform === "darwin") {
+        var helperPath = path.resolve(newDirPath, "Content Shell.app", "Contents", "Frameworks", "Content Shell Helper.app", "Contents", "MacOS", "Content Shell Helper");
+        fs.chmodSync(helperPath, "755");
+    }
+    return dest;
+}
+
+function getContentShellBinaryPath(dirPath)
+{
+    if (process.platform === "linux") {
+        return path.resolve(dirPath, "content_shell");
+    }
+    if (process.platform === "win32") {
+        return path.resolve(dirPath, "content_shell.exe");
+    }
+    if (process.platform === "darwin") {
+        return path.resolve(dirPath, "Content Shell.app", "Contents", "MacOS", "Content Shell");
+    }
+}
+
+function buildAndTest(buildDirectoryPath)
+{
+    var contentShellResourcesPath = path.resolve(buildDirectoryPath, "Release", "resources");
+    build(contentShellResourcesPath);
+    if (IS_BUILD_ONLY)
+        return;
+    runTests(buildDirectoryPath);
+}
+
+function build(contentShellResourcesPath)
+{
+    var devtoolsResourcesPath = path.resolve(contentShellResourcesPath, "inspector");
+    var copiedFrontendPath = path.resolve(devtoolsResourcesPath, "front_end");
+    var debugPath = path.resolve(devtoolsResourcesPath, "debug");
+    utils.removeRecursive(copiedFrontendPath);
+    utils.removeRecursive(debugPath);
+    utils.copyRecursive(SOURCE_PATH, devtoolsResourcesPath);
+    fs.renameSync(copiedFrontendPath, debugPath);
+    var inspectorBackendCommandsPath = path.resolve(devtoolsResourcesPath, "InspectorBackendCommands.js");
+    var supportedCSSPropertiesPath = path.resolve(devtoolsResourcesPath, "SupportedCSSProperties.js");
+    utils.copy(inspectorBackendCommandsPath, debugPath);
+    utils.copy(supportedCSSPropertiesPath, debugPath);
+}
+
+function runTests(buildDirectoryPath)
+{
+    var testArgs = [
+        "--additional-drt-flag=--debug-devtools",
+        "--no-pixel-tests",
+        "--build-directory",
+        buildDirectoryPath,
+    ].concat(getInspectorTests());
+    if (IS_DEBUG_ENABLED) {
+        testArgs.push("--additional-drt-flag=--remote-debugging-port=9222");
+        testArgs.push("--time-out-ms=6000000");
+        console.log("\n=============================================");
+        console.log("Go to: http://localhost:9222/");
+        console.log("Click on link and in console execute: test()");
+        console.log("=============================================\n");
+    }
+    var args = [BLINK_TEST_PATH].concat(testArgs).concat(getTestFlags());
+    console.log(`Running layout tests with args: ${args}`);
+    childProcess.spawn(PYTHON, args, {stdio: "inherit"});
+}
+
+function getTestFlags()
+{
+    var flagValues = Object.keys(Flags).map(key => Flags[key]);
+    return process.argv
+        .slice(2)
+        .filter(arg => !utils.includes(flagValues, arg) && !utils.includes(arg, "inspector"));
+}
+
+function getInspectorTests()
+{
+    var specificTests = process.argv.filter(arg => utils.includes(arg, "inspector"));
+    if (specificTests.length)
+        return specificTests;
+    return [
+        "inspector*",
+        "http/tests/inspector*",
+    ];
+}
\ No newline at end of file
diff --git a/third_party/WebKit/Source/devtools/scripts/unzip.py b/third_party/WebKit/Source/devtools/scripts/unzip.py
new file mode 100755
index 0000000..68f5eae
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/scripts/unzip.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+import zipfile
+
+if len(sys.argv) < 3:
+    print('Usage: {} <src> <dest>'.format(sys.argv[0]))
+    print(' <src> full path to zip file to be extracted')
+    print(' <dest> full path to destination folder')
+    sys.exit(1)
+
+src = sys.argv[1]
+dest = sys.argv[2]
+
+zip_ref = zipfile.ZipFile(src, 'r')
+zip_ref.extractall(dest)
+zip_ref.close()
diff --git a/third_party/WebKit/Source/devtools/scripts/utils.js b/third_party/WebKit/Source/devtools/scripts/utils.js
index c66e6a0..a0df35a 100644
--- a/third_party/WebKit/Source/devtools/scripts/utils.js
+++ b/third_party/WebKit/Source/devtools/scripts/utils.js
@@ -5,6 +5,7 @@
 var fs = require("fs");
 var http = require("http");
 var https = require("https");
+var path = require("path");
 var parseURL = require("url").parse;
 var Stream = require("stream").Transform;
 
@@ -45,18 +46,88 @@
     return new Buffer(str, "base64").toString("binary");
 }
 
-
 function isFile(path)
 {
     try {
         return fs.statSync(path).isFile();
-    } catch (e) {
+    } catch (error) {
         return false;
     }
 }
 
+function isDir(path)
+{
+    try {
+        return fs.statSync(path).isDirectory();
+    } catch (error) {
+        return false;
+    }
+}
+
+function copy(src, dest)
+{
+    try {
+        var targetFilePath = path.resolve(dest, path.basename(src));
+        fs.writeFileSync(targetFilePath, fs.readFileSync(src));
+    } catch (error) {
+        throw new Error(`Received an error: [${error}] while trying to copy: ${src} -> ${dest}`);
+    }
+}
+
+function copyRecursive(src, dest)
+{
+    try {
+        var targetDirPath = path.resolve(dest, path.basename(src));
+        if (!fs.existsSync(targetDirPath))
+            fs.mkdirSync(targetDirPath);
+        if (isDir(src)) {
+            var files = fs.readdirSync(src);
+            for (var i = 0; i < files.length; i++) {
+                var childPath = path.resolve(src, files[i]);
+                if (isDir(childPath)) {
+                    copyRecursive(childPath, targetDirPath);
+                } else {
+                    var targetFilePath =  path.resolve(targetDirPath, path.basename(childPath));
+                    fs.writeFileSync(targetFilePath, fs.readFileSync(childPath));
+                }
+            }
+        }
+    } catch (error) {
+        throw new Error(`Received an error: [${error}] while trying to copy: ${src} -> ${dest}`);
+    }
+}
+
+function removeRecursive(filePath)
+{
+    try {
+        if (fs.existsSync(filePath)) {
+            var files = fs.readdirSync(filePath);
+            for (var i = 0; i < files.length; i++) {
+                var childPath = path.resolve(filePath, files[i]);
+                if (isDir(childPath))
+                    removeRecursive(childPath);
+                else
+                    fs.unlinkSync(childPath);
+            }
+            fs.rmdirSync(filePath);
+        }
+    } catch (error) {
+        throw new Error(`Received an error: [${error}] while trying to remove: ${filePath}`);
+    }
+}
+
+function includes(sequence, target)
+{
+    return sequence.indexOf(target) > -1;
+}
+
 module.exports = {
     fetch,
     atob,
     isFile,
+    isDir,
+    copy,
+    copyRecursive,
+    removeRecursive,
+    includes,
 };
\ No newline at end of file
diff --git a/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp b/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
index 2f8dca03..b16cd1d4e 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXLayoutObject.cpp
@@ -328,7 +328,6 @@
   if (getLayoutObject() && getLayoutObject()->isTextControl())
     return true;
 
-  m_layoutObject->document().updateStyleAndLayoutTree();
   if (getNode() && hasEditableStyle(*getNode()))
     return true;
 
@@ -347,7 +346,6 @@
 // Requires layoutObject to be present because it relies on style
 // user-modify. Don't move this logic to AXNodeObject.
 bool AXLayoutObject::isRichlyEditable() const {
-  m_layoutObject->document().updateStyleAndLayoutTree();
   if (getNode() && hasRichlyEditableStyle(*getNode()))
     return true;
 
diff --git a/third_party/WebKit/Source/modules/encryptedmedia/ContentDecryptionModuleResultPromise.cpp b/third_party/WebKit/Source/modules/encryptedmedia/ContentDecryptionModuleResultPromise.cpp
index f11d53c..3bd4dd7 100644
--- a/third_party/WebKit/Source/modules/encryptedmedia/ContentDecryptionModuleResultPromise.cpp
+++ b/third_party/WebKit/Source/modules/encryptedmedia/ContentDecryptionModuleResultPromise.cpp
@@ -6,6 +6,7 @@
 
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptState.h"
+#include "bindings/core/v8/V8ThrowException.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/ExecutionContext.h"
 #include "core/dom/ExecutionContextTask.h"
@@ -17,22 +18,16 @@
 ExceptionCode WebCdmExceptionToExceptionCode(
     WebContentDecryptionModuleException cdmException) {
   switch (cdmException) {
+    case WebContentDecryptionModuleExceptionTypeError:
+      return V8TypeError;
     case WebContentDecryptionModuleExceptionNotSupportedError:
       return NotSupportedError;
     case WebContentDecryptionModuleExceptionInvalidStateError:
       return InvalidStateError;
-    case WebContentDecryptionModuleExceptionInvalidAccessError:
-      return InvalidAccessError;
     case WebContentDecryptionModuleExceptionQuotaExceededError:
       return QuotaExceededError;
     case WebContentDecryptionModuleExceptionUnknownError:
       return UnknownError;
-    case WebContentDecryptionModuleExceptionClientError:
-    case WebContentDecryptionModuleExceptionOutputError:
-      // Currently no matching DOMException for these 2 errors.
-      // FIXME: Update DOMException to handle these if actually added to
-      // the EME spec.
-      return UnknownError;
   }
 
   NOTREACHED();
@@ -98,7 +93,10 @@
                                                   const String& errorMessage) {
   DCHECK(isValidToFulfillPromise());
 
-  m_resolver->reject(DOMException::create(code, errorMessage));
+  ScriptState::Scope scope(m_resolver->getScriptState());
+  v8::Isolate* isolate = m_resolver->getScriptState()->isolate();
+  m_resolver->reject(
+      V8ThrowException::createDOMException(isolate, code, errorMessage));
   m_resolver.clear();
 }
 
diff --git a/third_party/WebKit/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp b/third_party/WebKit/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
index 9db401a..9f7e9c7 100644
--- a/third_party/WebKit/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
+++ b/third_party/WebKit/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
@@ -8,7 +8,7 @@
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "bindings/core/v8/ScriptState.h"
-#include "bindings/core/v8/V8Binding.h"
+#include "bindings/core/v8/V8ThrowException.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/DOMTypedArray.h"
 #include "core/dom/ExceptionCode.h"
@@ -58,7 +58,7 @@
 typedef Function<void(ExceptionCode, const String&)> FailureCallback;
 
 // Represents the result used when setContentDecryptionModule() is called.
-// Calls |success| if result is resolved, |failure| is result is rejected.
+// Calls |success| if result is resolved, |failure| if result is rejected.
 class SetContentDecryptionModuleResult final
     : public ContentDecryptionModuleResult {
  public:
@@ -96,6 +96,7 @@
       result.appendNumber(systemCode);
       result.append(')');
     }
+
     (*m_failureCallback)(WebCdmExceptionToExceptionCode(code),
                          result.toString());
   }
@@ -250,7 +251,9 @@
       !HTMLMediaElementEncryptedMedia::from(*m_element).m_isAttachingMediaKeys);
 
   // Reject promise with an appropriate error.
-  reject(DOMException::create(code, errorMessage));
+  ScriptState::Scope scope(getScriptState());
+  v8::Isolate* isolate = getScriptState()->isolate();
+  reject(V8ThrowException::createDOMException(isolate, code, errorMessage));
 }
 
 void SetMediaKeysHandler::clearFailed(ExceptionCode code,
diff --git a/third_party/WebKit/Source/modules/encryptedmedia/MediaKeySession.cpp b/third_party/WebKit/Source/modules/encryptedmedia/MediaKeySession.cpp
index 4a21564..b248dba 100644
--- a/third_party/WebKit/Source/modules/encryptedmedia/MediaKeySession.cpp
+++ b/third_party/WebKit/Source/modules/encryptedmedia/MediaKeySession.cpp
@@ -29,6 +29,7 @@
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "bindings/core/v8/ScriptState.h"
+#include "bindings/core/v8/V8ThrowException.h"
 #include "core/dom/DOMArrayBuffer.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/ExceptionCode.h"
@@ -81,6 +82,24 @@
   return true;
 }
 
+static bool IsPersistentSessionType(WebEncryptedMediaSessionType sessionType) {
+  // This implements section 5.1.1 Is persistent session type? from
+  // https://w3c.github.io/encrypted-media/#is-persistent-session-type
+  switch (sessionType) {
+    case WebEncryptedMediaSessionType::Temporary:
+      return false;
+    case WebEncryptedMediaSessionType::PersistentLicense:
+      return true;
+    case WebEncryptedMediaSessionType::PersistentReleaseMessage:
+      return true;
+    case blink::WebEncryptedMediaSessionType::Unknown:
+      break;
+  }
+
+  NOTREACHED();
+  return false;
+}
+
 static String ConvertKeyStatusToString(
     const WebEncryptedMediaKeyInformation::KeyStatus status) {
   switch (status) {
@@ -111,6 +130,13 @@
       DOMException::create(InvalidStateError, "The session is not callable."));
 }
 
+static ScriptPromise CreateRejectedPromiseAlreadyClosed(
+    ScriptState* scriptState) {
+  return ScriptPromise::rejectWithDOMException(
+      scriptState, DOMException::create(InvalidStateError,
+                                        "The session is already closed."));
+}
+
 static ScriptPromise CreateRejectedPromiseAlreadyInitialized(
     ScriptState* scriptState) {
   return ScriptPromise::rejectWithDOMException(
@@ -427,32 +453,38 @@
   // Generates a request based on the initData. When this method is invoked,
   // the user agent must run the following steps:
 
-  // 1. If this object's uninitialized value is false, return a promise
-  //    rejected with a new DOMException whose name is "InvalidStateError".
+  // 1. If this object is closed, return a promise rejected with an
+  //    InvalidStateError.
+  if (m_isClosed)
+    return CreateRejectedPromiseAlreadyClosed(scriptState);
+
+  // 2. If this object's uninitialized value is false, return a promise
+  //    rejected with an InvalidStateError.
   if (!m_isUninitialized)
     return CreateRejectedPromiseAlreadyInitialized(scriptState);
 
-  // 2. Let this object's uninitialized be false.
+  // 3. Let this object's uninitialized be false.
   m_isUninitialized = false;
 
-  // 3. If initDataType is an empty string, return a promise rejected with a
-  //    new DOMException whose name is "InvalidAccessError".
+  // 4. If initDataType is the empty string, return a promise rejected
+  //    with a newly created TypeError.
   if (initDataTypeString.isEmpty()) {
-    return ScriptPromise::rejectWithDOMException(
+    return ScriptPromise::reject(
         scriptState,
-        DOMException::create(InvalidAccessError,
-                             "The initDataType parameter is empty."));
+        V8ThrowException::createTypeError(
+            scriptState->isolate(), "The initDataType parameter is empty."));
   }
 
-  // 4. If initData is an empty array, return a promise rejected with a new
-  //    DOMException whose name is"InvalidAccessError".
+  // 5. If initData is an empty array, return a promise rejected with a
+  //    newly created TypeError.
   if (!initData.byteLength()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(InvalidAccessError,
+    return ScriptPromise::reject(
+        scriptState,
+        V8ThrowException::createTypeError(scriptState->isolate(),
                                           "The initData parameter is empty."));
   }
 
-  // 5. If the Key System implementation represented by this object's cdm
+  // 6. If the Key System implementation represented by this object's cdm
   //    implementation value does not support initDataType as an
   //    Initialization Data Type, return a promise rejected with a new
   //    DOMException whose name is NotSupportedError. String comparison
@@ -470,26 +502,26 @@
                                  initDataTypeString + "' is not supported."));
   }
 
-  // 6. Let init data be a copy of the contents of the initData parameter.
+  // 7. Let init data be a copy of the contents of the initData parameter.
   DOMArrayBuffer* initDataBuffer =
       DOMArrayBuffer::create(initData.data(), initData.byteLength());
 
-  // 7. Let session type be this object's session type.
+  // 8. Let session type be this object's session type.
   //    (Done in constructor.)
 
-  // 8. Let promise be a new promise.
+  // 9. Let promise be a new promise.
   NewSessionResultPromise* result =
       new NewSessionResultPromise(scriptState, this);
   ScriptPromise promise = result->promise();
 
-  // 9. Run the following steps asynchronously (documented in
-  //    actionTimerFired())
+  // 10. Run the following steps asynchronously (documented in
+  //     actionTimerFired())
   m_pendingActions.append(PendingAction::CreatePendingGenerateRequest(
       result, initDataType, initDataBuffer));
   DCHECK(!m_actionTimer.isActive());
   m_actionTimer.startOneShot(0, BLINK_FROM_HERE);
 
-  // 10. Return promise.
+  // 11. Return promise.
   return promise;
 }
 
@@ -502,39 +534,38 @@
   // Loads the data stored for the specified session into this object. When
   // this method is invoked, the user agent must run the following steps:
 
-  // 1. If this object's uninitialized value is false, return a promise
-  //    rejected with a new DOMException whose name is "InvalidStateError".
+  // 1. If this object is closed, return a promise rejected with an
+  //    InvalidStateError.
+  if (m_isClosed)
+    return CreateRejectedPromiseAlreadyClosed(scriptState);
+
+  // 2. If this object's uninitialized value is false, return a promise
+  //    rejected with an InvalidStateError.
   if (!m_isUninitialized)
     return CreateRejectedPromiseAlreadyInitialized(scriptState);
 
-  // 2. Let this object's uninitialized be false.
+  // 3. Let this object's uninitialized value be false.
   m_isUninitialized = false;
 
-  // 3. If sessionId is an empty string, return a promise rejected with a
-  //    new DOMException whose name is "InvalidAccessError".
+  // 4. If sessionId is the empty string, return a promise rejected with
+  //    a newly created TypeError.
   if (sessionId.isEmpty()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(InvalidAccessError,
+    return ScriptPromise::reject(
+        scriptState,
+        V8ThrowException::createTypeError(scriptState->isolate(),
                                           "The sessionId parameter is empty."));
   }
 
-  // 4. If this object's session type is not "persistent-license" or
-  //    "persistent-release-message", return a promise rejected with a
-  //    new DOMException whose name is InvalidAccessError.
-  if (m_sessionType != WebEncryptedMediaSessionType::PersistentLicense &&
-      m_sessionType != WebEncryptedMediaSessionType::PersistentReleaseMessage) {
-    return ScriptPromise::rejectWithDOMException(
+  // 5. If the result of running the "Is persistent session type?" algorithm
+  //    on this object's session type is false, return a promise rejected
+  //    with a newly created TypeError.
+  if (!IsPersistentSessionType(m_sessionType)) {
+    return ScriptPromise::reject(
         scriptState,
-        DOMException::create(InvalidAccessError,
-                             "The session type is not persistent."));
+        V8ThrowException::createTypeError(
+            scriptState->isolate(), "The session type is not persistent."));
   }
 
-  // 5. If the Key System implementation represented by this object's cdm
-  //    implementation value does not support loading previous sessions,
-  //    return a promise rejected with a new DOMException whose name is
-  //    NotSupportedError.
-  // FIXME: Implement this (http://crbug.com/448922).
-
   // 6. Let origin be the origin of this object's Document.
   //    (Available as getExecutionContext()->getSecurityOrigin() anytime.)
 
@@ -557,41 +588,46 @@
 ScriptPromise MediaKeySession::update(ScriptState* scriptState,
                                       const DOMArrayPiece& response) {
   DVLOG(MEDIA_KEY_SESSION_LOG_LEVEL) << __func__ << "(" << this << ")";
-  DCHECK(!m_isClosed);
 
   // From https://w3c.github.io/encrypted-media/#update:
   // Provides messages, including licenses, to the CDM. When this method is
   // invoked, the user agent must run the following steps:
 
-  // 1. If this object's callable value is false, return a promise rejected
-  //    with a new DOMException whose name is InvalidStateError.
+  // 1. If this object is closed, return a promise rejected with an
+  //    InvalidStateError.
+  if (m_isClosed)
+    return CreateRejectedPromiseAlreadyClosed(scriptState);
+
+  // 2. If this object's callable value is false, return a promise
+  //    rejected with an InvalidStateError.
   if (!m_isCallable)
     return CreateRejectedPromiseNotCallable(scriptState);
 
-  // 2. If response is an empty array, return a promise rejected with a
-  //    new DOMException whose name is InvalidAccessError.
+  // 3. If response is an empty array, return a promise rejected with a
+  //    newly created TypeError.
   if (!response.byteLength()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(InvalidAccessError,
+    return ScriptPromise::reject(
+        scriptState,
+        V8ThrowException::createTypeError(scriptState->isolate(),
                                           "The response parameter is empty."));
   }
 
-  // 3. Let response copy be a copy of the contents of the response parameter.
+  // 4. Let response copy be a copy of the contents of the response parameter.
   DOMArrayBuffer* responseCopy =
       DOMArrayBuffer::create(response.data(), response.byteLength());
 
-  // 4. Let promise be a new promise.
+  // 5. Let promise be a new promise.
   SimpleResultPromise* result = new SimpleResultPromise(scriptState, this);
   ScriptPromise promise = result->promise();
 
-  // 5. Run the following steps asynchronously (documented in
+  // 6. Run the following steps asynchronously (documented in
   //    actionTimerFired())
   m_pendingActions.append(
       PendingAction::CreatePendingUpdate(result, responseCopy));
   if (!m_actionTimer.isActive())
     m_actionTimer.startOneShot(0, BLINK_FROM_HERE);
 
-  // 6. Return promise.
+  // 7. Return promise.
   return promise;
 }
 
@@ -634,29 +670,24 @@
   // Removes stored session data associated with this object. When this
   // method is invoked, the user agent must run the following steps:
 
-  // 1. If this object's callable value is false, return a promise rejected
-  //    with a new DOMException whose name is "InvalidStateError".
+  // 1. If this object is closed, return a promise rejected with an
+  //    InvalidStateError.
+  if (m_isClosed)
+    return CreateRejectedPromiseAlreadyClosed(scriptState);
+
+  // 2. If this object's callable value is false, return a promise rejected
+  //    with an InvalidStateError.
   if (!m_isCallable)
     return CreateRejectedPromiseNotCallable(scriptState);
 
-  // 2. If this object's session type is not "persistent-license" or
-  //    "persistent-release-message", return a promise rejected with a
-  //    new DOMException whose name is InvalidAccessError.
-  if (m_sessionType != WebEncryptedMediaSessionType::PersistentLicense &&
-      m_sessionType != WebEncryptedMediaSessionType::PersistentReleaseMessage) {
-    return ScriptPromise::rejectWithDOMException(
+  // 3. If the result of running the "Is persistent session type?" algorithm
+  //    on this object's session type is false, return a promise rejected
+  //    with a newly created TypeError.
+  if (!IsPersistentSessionType(m_sessionType)) {
+    return ScriptPromise::reject(
         scriptState,
-        DOMException::create(InvalidAccessError,
-                             "The session type is not persistent."));
-  }
-
-  // 3. If the Session Close algorithm has been run on this object, return a
-  //    promise rejected with a new DOMException whose name is
-  //    "InvalidStateError".
-  if (m_isClosed) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(InvalidStateError,
-                                          "The session is already closed."));
+        V8ThrowException::createTypeError(
+            scriptState->isolate(), "The session type is not persistent."));
   }
 
   // 4. Let promise be a new promise.
@@ -687,18 +718,18 @@
 
     switch (action->getType()) {
       case PendingAction::GenerateRequest:
-        // NOTE: Continue step 9 of MediaKeySession::generateRequest().
+        // NOTE: Continue step 10 of MediaKeySession::generateRequest().
         DVLOG(MEDIA_KEY_SESSION_LOG_LEVEL) << __func__ << "(" << this
                                            << ") GenerateRequest";
 
-        // initializeNewSession() in Chromium will execute steps 9.1 to 9.7.
+        // initializeNewSession() in Chromium will execute steps 10.1 to 10.9.
         m_session->initializeNewSession(
             action->initDataType(),
             static_cast<unsigned char*>(action->data()->data()),
             action->data()->byteLength(), m_sessionType,
             action->result()->result());
 
-        // Remaining steps (from 9.8) executed in finishGenerateRequest(),
+        // Remaining steps (from 10.10) executed in finishGenerateRequest(),
         // called when |result| is resolved.
         break;
 
@@ -712,11 +743,11 @@
         //     validate the sessionId value before passing it to the CDM.
         //     At a minimum, this should include checking that the length
         //     and value (e.g. alphanumeric) are reasonable.
-        // 8.2 If the previous step failed, reject promise with a new
-        //     DOMException whose name is "InvalidAccessError".
+        // 8.2 If the preceding step failed, or if sanitized session ID
+        //     is empty, reject promise with a newly created TypeError.
         if (!isValidSessionId(action->sessionId())) {
           action->result()->completeWithError(
-              WebContentDecryptionModuleExceptionInvalidAccessError, 0,
+              WebContentDecryptionModuleExceptionTypeError, 0,
               "Invalid sessionId");
           return;
         }
diff --git a/third_party/WebKit/Source/modules/encryptedmedia/MediaKeys.cpp b/third_party/WebKit/Source/modules/encryptedmedia/MediaKeys.cpp
index ff36062..903174d 100644
--- a/third_party/WebKit/Source/modules/encryptedmedia/MediaKeys.cpp
+++ b/third_party/WebKit/Source/modules/encryptedmedia/MediaKeys.cpp
@@ -25,7 +25,9 @@
 
 #include "modules/encryptedmedia/MediaKeys.h"
 
+#include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptState.h"
+#include "bindings/core/v8/V8ThrowException.h"
 #include "core/dom/DOMArrayBuffer.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/ExceptionCode.h"
@@ -190,20 +192,21 @@
   // The setServerCertificate(serverCertificate) method provides a server
   // certificate to be used to encrypt messages to the license server.
   // It must run the following steps:
-  // 1. If serverCertificate is an empty array, return a promise rejected
-  //    with a new DOMException whose name is "InvalidAccessError".
+  // 1. If the Key System implementation represented by this object's cdm
+  //    implementation value does not support server certificates, return
+  //    a promise resolved with false.
+  // TODO(jrummell): Provide a way to determine if the CDM supports this.
+  // http://crbug.com/647816.
+  //
+  // 2. If serverCertificate is an empty array, return a promise rejected
+  //    with a new a newly created TypeError.
   if (!serverCertificate.byteLength()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState,
-        DOMException::create(InvalidAccessError,
-                             "The serverCertificate parameter is empty."));
+    return ScriptPromise::reject(
+        scriptState, V8ThrowException::createTypeError(
+                         scriptState->isolate(),
+                         "The serverCertificate parameter is empty."));
   }
 
-  // 2. If the keySystem does not support server certificates, return a
-  //    promise rejected with a new DOMException whose name is
-  //    "NotSupportedError".
-  //    (Let the CDM decide whether to support this or not.)
-
   // 3. Let certificate be a copy of the contents of the serverCertificate
   //    parameter.
   DOMArrayBuffer* serverCertificateBuffer = DOMArrayBuffer::create(
diff --git a/third_party/WebKit/Source/modules/encryptedmedia/NavigatorRequestMediaKeySystemAccess.cpp b/third_party/WebKit/Source/modules/encryptedmedia/NavigatorRequestMediaKeySystemAccess.cpp
index 510e1dc..a81ab3a 100644
--- a/third_party/WebKit/Source/modules/encryptedmedia/NavigatorRequestMediaKeySystemAccess.cpp
+++ b/third_party/WebKit/Source/modules/encryptedmedia/NavigatorRequestMediaKeySystemAccess.cpp
@@ -4,8 +4,10 @@
 
 #include "modules/encryptedmedia/NavigatorRequestMediaKeySystemAccess.h"
 
+#include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "bindings/core/v8/ScriptState.h"
+#include "bindings/core/v8/V8ThrowException.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/Document.h"
 #include "core/dom/ExceptionCode.h"
@@ -102,9 +104,7 @@
       const override {
     return m_supportedConfigurations;
   }
-  SecurityOrigin* getSecurityOrigin() const override {
-    return m_resolver->getExecutionContext()->getSecurityOrigin();
-  }
+  SecurityOrigin* getSecurityOrigin() const override;
   void requestSucceeded(WebContentDecryptionModuleAccess*) override;
   void requestNotSupported(const WebString& errorMessage) override;
 
@@ -116,6 +116,9 @@
   }
 
  private:
+  // Returns true if the ExecutionContext is valid, false otherwise.
+  bool isExecutionContextValid() const;
+
   // For widevine key system, generate warning and report to UMA if
   // |m_supportedConfigurations| contains any video capability with empty
   // robustness string.
@@ -192,11 +195,20 @@
   checkVideoCapabilityRobustness();
 }
 
+SecurityOrigin* MediaKeySystemAccessInitializer::getSecurityOrigin() const {
+  return isExecutionContextValid()
+             ? m_resolver->getExecutionContext()->getSecurityOrigin()
+             : nullptr;
+}
+
 void MediaKeySystemAccessInitializer::requestSucceeded(
     WebContentDecryptionModuleAccess* access) {
   checkEmptyCodecs(access->getConfiguration());
   checkCapabilities(access->getConfiguration());
 
+  if (!isExecutionContextValid())
+    return;
+
   m_resolver->resolve(
       new MediaKeySystemAccess(m_keySystem, wrapUnique(access)));
   m_resolver.clear();
@@ -204,10 +216,21 @@
 
 void MediaKeySystemAccessInitializer::requestNotSupported(
     const WebString& errorMessage) {
+  if (!isExecutionContextValid())
+    return;
+
   m_resolver->reject(DOMException::create(NotSupportedError, errorMessage));
   m_resolver.clear();
 }
 
+bool MediaKeySystemAccessInitializer::isExecutionContextValid() const {
+  // activeDOMObjectsAreStopped() is called to see if the context is in the
+  // process of being destroyed. If it is true, assume the context is no
+  // longer valid as it is about to be destroyed anyway.
+  ExecutionContext* context = m_resolver->getExecutionContext();
+  return context && !context->activeDOMObjectsAreStopped();
+}
+
 void MediaKeySystemAccessInitializer::checkVideoCapabilityRobustness() const {
   // Only check for widevine key system.
   if (keySystem() != "com.widevine.alpha")
@@ -312,21 +335,21 @@
 
   // From https://w3c.github.io/encrypted-media/#requestMediaKeySystemAccess
   // When this method is invoked, the user agent must run the following steps:
-  // 1. If keySystem is an empty string, return a promise rejected with a
-  //    new DOMException whose name is InvalidAccessError.
+  // 1. If keySystem is the empty string, return a promise rejected with a
+  //    newly created TypeError.
   if (keySystem.isEmpty()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(InvalidAccessError,
+    return ScriptPromise::reject(
+        scriptState,
+        V8ThrowException::createTypeError(scriptState->isolate(),
                                           "The keySystem parameter is empty."));
   }
 
-  // 2. If supportedConfigurations was provided and is empty, return a
-  //    promise rejected with a new DOMException whose name is
-  //    InvalidAccessError.
+  // 2. If supportedConfigurations is empty, return a promise rejected with
+  //    a newly created TypeError.
   if (!supportedConfigurations.size()) {
-    return ScriptPromise::rejectWithDOMException(
-        scriptState, DOMException::create(
-                         InvalidAccessError,
+    return ScriptPromise::reject(
+        scriptState, V8ThrowException::createTypeError(
+                         scriptState->isolate(),
                          "The supportedConfigurations parameter is empty."));
   }
 
diff --git a/third_party/WebKit/Source/modules/notifications/Notification.cpp b/third_party/WebKit/Source/modules/notifications/Notification.cpp
index eea467b..af9ad5b 100644
--- a/third_party/WebKit/Source/modules/notifications/Notification.cpp
+++ b/third_party/WebKit/Source/modules/notifications/Notification.cpp
@@ -137,8 +137,7 @@
       ActiveDOMObject(context),
       m_type(type),
       m_state(State::Loading),
-      m_data(data),
-      m_requestedClose(false) {
+      m_data(data) {
   DCHECK(notificationManager());
 }
 
@@ -157,7 +156,7 @@
   DCHECK_EQ(m_state, State::Loading);
   if (NotificationManager::from(getExecutionContext())->permissionStatus() !=
       mojom::blink::PermissionStatus::GRANTED) {
-    dispatchEvent(Event::create(EventTypeNames::error));
+    dispatchErrorEvent();
     return;
   }
 
@@ -180,55 +179,48 @@
 }
 
 void Notification::close() {
-  if (m_state != State::Showing) {
-    // TODO(peter): Abort the load instead of closing the notification after
-    // it's completed.
-    if (m_state == State::Loading || m_notificationId.isEmpty())
-      m_requestedClose = true;
-
+  if (m_state != State::Showing)
     return;
-  }
 
   // Schedule the "close" event to be fired for non-persistent notifications.
   // Persistent notifications won't get such events for programmatic closes.
   if (m_type == Type::NonPersistent) {
     getExecutionContext()->postTask(
-        BLINK_FROM_HERE,
-        createSameThreadTask(&Notification::didCloseNotification,
-                             wrapPersistent(this)));
+        BLINK_FROM_HERE, createSameThreadTask(&Notification::dispatchCloseEvent,
+                                              wrapPersistent(this)));
     m_state = State::Closing;
-  } else {
-    m_state = State::Closed;
+
+    notificationManager()->close(this);
+    return;
   }
 
+  m_state = State::Closed;
+
   SecurityOrigin* origin = getExecutionContext()->getSecurityOrigin();
   DCHECK(origin);
 
-  notificationManager()->close(WebSecurityOrigin(origin), m_data.tag,
-                               m_notificationId);
+  notificationManager()->closePersistent(WebSecurityOrigin(origin), m_data.tag,
+                                         m_notificationId);
 }
 
-void Notification::didShowNotification(const WebString& notificationId) {
-  DCHECK(m_notificationId.isEmpty());
-  m_notificationId = notificationId;
-
+void Notification::dispatchShowEvent() {
   dispatchEvent(Event::create(EventTypeNames::show));
-
-  if (m_requestedClose)
-    close();
 }
 
-void Notification::didClickNotification() {
+void Notification::dispatchClickEvent() {
   UserGestureIndicator gestureIndicator(
       UserGestureToken::create(UserGestureToken::NewGesture));
-
   ScopedWindowFocusAllowedIndicator windowFocusAllowed(getExecutionContext());
   dispatchEvent(Event::create(EventTypeNames::click));
 }
 
-void Notification::didCloseNotification() {
-  // The notification will be showing when the user initiated the close, or it
-  // will be closing if the developer initiated the close.
+void Notification::dispatchErrorEvent() {
+  dispatchEvent(Event::create(EventTypeNames::error));
+}
+
+void Notification::dispatchCloseEvent() {
+  // The notification should be Showing if the user initiated the close, or it
+  // should be Closing if the developer initiated the close.
   if (m_state != State::Showing && m_state != State::Closing)
     return;
 
diff --git a/third_party/WebKit/Source/modules/notifications/Notification.h b/third_party/WebKit/Source/modules/notifications/Notification.h
index 0d1b8eb..ef5b78d 100644
--- a/third_party/WebKit/Source/modules/notifications/Notification.h
+++ b/third_party/WebKit/Source/modules/notifications/Notification.h
@@ -90,9 +90,10 @@
   DEFINE_ATTRIBUTE_EVENT_LISTENER(close);
 
   // WebNotificationDelegate interface.
-  void didShowNotification(const WebString& notificationId) override;
-  void didClickNotification() override;
-  void didCloseNotification() override;
+  void dispatchShowEvent() override;
+  void dispatchClickEvent() override;
+  void dispatchErrorEvent() override;
+  void dispatchCloseEvent() override;
 
   String title() const;
   String dir() const;
@@ -174,10 +175,6 @@
 
   String m_notificationId;
 
-  // Whether the developer has requested the notification to be closed whilst
-  // it's still in process of being shown.
-  bool m_requestedClose;
-
   Member<AsyncMethodRunner<Notification>> m_prepareShowMethodRunner;
 
   Member<NotificationResourcesLoader> m_loader;
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
index 432aa28..3f32fdb 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
@@ -187,7 +187,7 @@
 ReloadwithoutSubResourceCacheRevalidation
 RemotePlayback status=experimental
 RenderingPipelineThrottling status=stable
-RenderUnicodeControlCharacters status=experimental
+RenderUnicodeControlCharacters status=stable
 ResizeObserver status=experimental
 // Handles frame scrolling via the root PaintLayer instead of the FrameView.
 // crbug.com/417782 tracks enabling this by default.
diff --git a/third_party/WebKit/Source/web/WebSettingsImpl.cpp b/third_party/WebKit/Source/web/WebSettingsImpl.cpp
index 736c51a..8fa5aac 100644
--- a/third_party/WebKit/Source/web/WebSettingsImpl.cpp
+++ b/third_party/WebKit/Source/web/WebSettingsImpl.cpp
@@ -665,10 +665,6 @@
   m_settings->setSmartInsertDeleteEnabled(enabled);
 }
 
-void WebSettingsImpl::setPinchOverlayScrollbarThickness(int thickness) {
-  m_settings->setPinchOverlayScrollbarThickness(thickness);
-}
-
 void WebSettingsImpl::setUseSolidColorScrollbars(bool enabled) {
   m_settings->setUseSolidColorScrollbars(enabled);
 }
diff --git a/third_party/WebKit/Source/web/WebSettingsImpl.h b/third_party/WebKit/Source/web/WebSettingsImpl.h
index c83fc04..e3238e7 100644
--- a/third_party/WebKit/Source/web/WebSettingsImpl.h
+++ b/third_party/WebKit/Source/web/WebSettingsImpl.h
@@ -135,7 +135,6 @@
   void setPerTilePaintingEnabled(bool) override;
   void setPictographFontFamily(const WebString&,
                                UScriptCode = USCRIPT_COMMON) override;
-  void setPinchOverlayScrollbarThickness(int) override;
   void setPluginsEnabled(bool) override;
   void setAvailablePointerTypes(int) override;
   void setPrimaryPointerType(PointerType) override;
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/wptserve.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/wptserve.py
index aaec9083..e9219a8 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/wptserve.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/wptserve.py
@@ -42,10 +42,7 @@
         if self._port_obj.host.filesystem.exists(path_to_ws_handlers):
             start_cmd += ['--ws_doc_root', path_to_ws_handlers]
 
-        # TODO(tkent): Do not suppress console output on Windows until
-        # crbug.com/623613 is resolved.
-        if not self._platform.is_win():
-            self._stdout = self._stderr = self._executive.DEVNULL
+        self._stdout = self._stderr = self._executive.DEVNULL
         # TODO(burnik): We should stop setting the CWD once WPT can be run without it.
         self._cwd = path_to_wpt_root
         self._env = port_obj.host.environ.copy()
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/01.pem b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/01.pem
new file mode 100644
index 0000000..ff31326
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/01.pem
@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=web-platform-tests
+        Validity
+            Not Before: Oct 18 08:36:53 2016 GMT
+            Not After : Jan  4 08:36:53 2025 GMT
+        Subject: CN=web-platform-tests
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c9:e3:69:85:18:ad:1a:df:ba:6c:05:96:0c:c1:
+                    22:51:78:3a:90:42:88:72:f7:f4:8c:81:59:43:5b:
+                    ea:13:0e:e7:ac:27:c2:72:55:c6:53:48:4f:08:37:
+                    a1:5f:e1:5c:78:24:d3:12:2d:2b:71:c3:51:a3:ad:
+                    0b:c6:a1:95:2d:f5:cf:db:50:64:c8:fa:d0:2a:bc:
+                    ae:a8:2b:44:53:a2:a9:e8:55:92:b7:95:4f:d2:f3:
+                    d9:f1:e1:0b:17:11:ad:62:50:c5:57:4d:43:ed:fc:
+                    6f:14:99:52:ab:27:f4:59:45:f8:86:77:d1:fc:00:
+                    8a:b3:4c:7e:66:1a:b3:04:58:e5:9e:57:52:b8:9d:
+                    ef:cc:ef:59:6e:a0:99:2c:e1:e1:1b:da:19:be:61:
+                    f9:c3:89:76:1e:88:1e:86:64:1f:5b:27:3e:44:7c:
+                    ad:d0:04:b0:57:db:79:bd:1e:7d:4a:73:52:c9:2d:
+                    16:6d:f0:80:2e:07:70:09:2f:ca:a2:c3:12:ae:2c:
+                    51:3d:d4:f3:4e:f6:53:c2:9d:e0:6c:56:6d:36:38:
+                    bc:1e:4f:d9:7e:1e:b9:26:fe:2f:f0:d6:6b:af:02:
+                    bf:4d:93:4b:a7:e7:bd:eb:e7:79:69:b1:65:1f:88:
+                    f0:fd:cc:03:ce:5e:00:83:b9:ad:70:6a:95:78:5b:
+                    97:fb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+            X509v3 Authority Key Identifier: 
+                keyid:4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+                DirName:/CN=web-platform-tests
+                serial:01
+
+            X509v3 Key Usage: 
+                Certificate Sign
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+         c6:f0:d9:1e:24:82:3a:b8:d4:b3:92:00:ea:0e:d6:17:e9:8d:
+         a0:7a:35:bc:59:b9:2d:b0:6b:c2:27:74:66:6f:26:18:c2:75:
+         14:4c:29:76:3d:28:3c:6e:66:be:3c:27:55:17:09:f3:f4:2d:
+         1b:ab:b5:b5:5c:f6:5c:24:d1:7d:35:45:30:7e:41:19:79:f8:
+         a1:38:ca:ce:30:35:e0:de:1c:77:f8:fe:cd:dd:25:4c:13:18:
+         d7:9e:8b:35:04:58:24:7e:a3:f5:89:48:fc:28:e7:11:88:ec:
+         b8:37:e5:2f:8b:61:b9:f3:44:12:6a:62:cb:b3:16:5f:61:77:
+         8c:e6:d5:3f:ec:43:9a:22:7b:d2:e0:3a:25:15:49:e4:75:09:
+         a3:9e:82:64:a9:3f:94:86:c7:a8:87:a2:34:51:87:c9:58:89:
+         76:42:00:bc:7c:d3:17:7a:40:79:b1:f5:ac:0d:9d:14:a5:db:
+         57:7f:10:36:21:79:7e:8f:62:91:68:d2:1f:c9:d3:76:18:cf:
+         98:13:bf:21:a1:09:c3:40:17:9e:16:80:f4:61:23:46:cf:78:
+         9b:00:a7:fa:53:00:b5:67:d3:82:84:79:50:c0:ae:e0:0f:51:
+         55:84:e8:5a:cd:c7:6a:83:11:de:82:58:70:11:b8:1e:67:ab:
+         30:1d:a6:82
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjegAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTYxMDE4MDgzNjUzWhcNMjUwMTA0MDgzNjUzWjAd
+MRswGQYDVQQDDBJ3ZWItcGxhdGZvcm0tdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDJ42mFGK0a37psBZYMwSJReDqQQohy9/SMgVlDW+oTDues
+J8JyVcZTSE8IN6Ff4Vx4JNMSLStxw1GjrQvGoZUt9c/bUGTI+tAqvK6oK0RToqno
+VZK3lU/S89nx4QsXEa1iUMVXTUPt/G8UmVKrJ/RZRfiGd9H8AIqzTH5mGrMEWOWe
+V1K4ne/M71luoJks4eEb2hm+YfnDiXYeiB6GZB9bJz5EfK3QBLBX23m9Hn1Kc1LJ
+LRZt8IAuB3AJL8qiwxKuLFE91PNO9lPCneBsVm02OLweT9l+Hrkm/i/w1muvAr9N
+k0un573r53lpsWUfiPD9zAPOXgCDua1wapV4W5f7AgMBAAGjgZkwgZYwDAYDVR0T
+BAUwAwEB/zAdBgNVHQ4EFgQUTmUNO5KzWks0dfspJspTa6FlFX4wRQYDVR0jBD4w
+PIAUTmUNO5KzWks0dfspJspTa6FlFX6hIaQfMB0xGzAZBgNVBAMMEndlYi1wbGF0
+Zm9ybS10ZXN0c4IBATALBgNVHQ8EBAMCAgQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
+DQYJKoZIhvcNAQELBQADggEBAMbw2R4kgjq41LOSAOoO1hfpjaB6NbxZuS2wa8In
+dGZvJhjCdRRMKXY9KDxuZr48J1UXCfP0LRurtbVc9lwk0X01RTB+QRl5+KE4ys4w
+NeDeHHf4/s3dJUwTGNeeizUEWCR+o/WJSPwo5xGI7Lg35S+LYbnzRBJqYsuzFl9h
+d4zm1T/sQ5oie9LgOiUVSeR1CaOegmSpP5SGx6iHojRRh8lYiXZCALx80xd6QHmx
+9awNnRSl21d/EDYheX6PYpFo0h/J03YYz5gTvyGhCcNAF54WgPRhI0bPeJsAp/pT
+ALVn04KEeVDAruAPUVWE6FrNx2qDEd6CWHARuB5nqzAdpoI=
+-----END CERTIFICATE-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/02.pem b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/02.pem
new file mode 100644
index 0000000..bb883ca
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/02.pem
@@ -0,0 +1,85 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=web-platform-tests
+        Validity
+            Not Before: Oct 18 08:36:53 2016 GMT
+            Not After : Jan  4 08:36:53 2025 GMT
+        Subject: CN=127.0.0.1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:aa:bd:55:d1:6f:96:08:8b:8f:72:bd:ce:70:2c:
+                    08:f5:28:8b:4c:03:ba:78:ad:d3:5e:a9:26:87:5f:
+                    45:ce:7d:6e:cc:18:f9:73:cb:34:e0:d6:50:bc:47:
+                    51:61:93:2d:4f:18:c5:61:77:9e:4e:8b:3f:eb:36:
+                    f0:b9:1c:a4:c8:3e:73:0e:c8:fd:9a:32:cc:22:28:
+                    46:00:c8:03:4a:46:42:bb:0d:06:c0:0e:87:1b:ca:
+                    91:b8:84:7d:c9:38:41:a8:be:f9:46:cf:9e:7a:f2:
+                    5c:12:98:9a:eb:34:fe:18:8e:e1:4d:f8:06:a9:43:
+                    00:2d:bb:8b:ec:ad:f9:3e:9e:82:5b:83:32:f0:47:
+                    2f:4c:aa:de:e6:83:27:0c:b3:3b:05:d9:ee:ee:1a:
+                    f2:cd:39:d8:15:3a:3a:9d:63:1e:63:ed:77:ab:d9:
+                    02:b1:63:b1:a0:62:49:4a:43:c5:9f:cc:e3:1d:7f:
+                    54:92:9f:5e:4a:d7:27:78:32:c1:fa:33:58:66:86:
+                    0b:44:3a:75:a6:05:2f:41:d3:3b:cb:f6:52:81:d3:
+                    c9:0d:7b:99:22:0e:b9:18:f8:49:b3:99:b3:59:24:
+                    cf:11:90:bb:bb:69:09:ef:9a:f9:8a:32:e0:a8:5c:
+                    30:97:75:9f:0b:4c:80:b1:a3:40:de:e5:8f:d9:b7:
+                    23:6f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                A1:21:E0:6B:F3:A6:1B:7D:8C:F0:05:33:61:C7:1D:7D:47:6D:66:99
+            X509v3 Authority Key Identifier: 
+                keyid:4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+
+            X509v3 Key Usage: 
+                Digital Signature, Non Repudiation, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Subject Alternative Name: 
+                DNS:127.0.0.1, DNS:www.127.0.0.1, DNS:xn--n8j6ds53lwwkrqhv28a.127.0.0.1, DNS:xn--lve-6lad.127.0.0.1, DNS:www2.127.0.0.1, DNS:www1.127.0.0.1
+    Signature Algorithm: sha256WithRSAEncryption
+         44:47:be:6b:b8:d7:34:79:ac:e7:a6:5a:5f:e8:2b:7f:c9:a9:
+         04:1a:d5:75:a7:d6:3e:f7:98:b3:eb:37:ed:7d:96:37:44:0c:
+         7c:ad:e9:8a:c6:50:69:56:63:91:3f:2b:b4:89:0c:f7:03:ae:
+         69:b1:b0:d6:00:33:a6:7c:83:db:56:d0:e9:d8:fc:53:be:ad:
+         b8:3e:3e:c4:e5:22:cd:d0:eb:a7:a5:75:f1:03:ba:bd:49:32:
+         0b:1b:b7:2f:56:a8:98:14:43:58:d4:23:05:7c:b9:13:00:85:
+         6e:70:e5:5a:45:1f:9e:4a:c0:06:72:93:38:24:9d:d2:d9:82:
+         99:c8:5f:c2:c6:f2:f9:53:49:e1:cb:13:72:aa:07:e8:f3:83:
+         a6:f4:4c:28:a6:94:7c:d7:e4:42:b0:de:d6:ec:0d:e2:af:2f:
+         cd:44:c4:b6:cb:54:7d:d7:1f:32:4d:97:31:c7:6e:13:14:0e:
+         a1:f8:6e:e4:b4:ee:15:83:fd:7e:29:e4:7b:06:35:d2:e3:9a:
+         6e:79:aa:a1:a2:91:65:98:48:46:1b:33:36:ab:26:c2:76:18:
+         94:82:ea:c0:a0:ca:9e:b1:98:5f:98:4d:d7:1c:89:b4:e9:72:
+         0d:fa:b7:a9:c2:69:c3:0a:ee:f3:3e:fd:90:87:c8:0c:33:31:
+         69:e1:c0:99
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTYxMDE4MDgzNjUzWhcNMjUwMTA0MDgzNjUzWjAU
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCqvVXRb5YIi49yvc5wLAj1KItMA7p4rdNeqSaHX0XOfW7MGPlzyzTg1lC8
+R1Fhky1PGMVhd55Oiz/rNvC5HKTIPnMOyP2aMswiKEYAyANKRkK7DQbADocbypG4
+hH3JOEGovvlGz5568lwSmJrrNP4YjuFN+AapQwAtu4vsrfk+noJbgzLwRy9Mqt7m
+gycMszsF2e7uGvLNOdgVOjqdYx5j7Xer2QKxY7GgYklKQ8WfzOMdf1SSn15K1yd4
+MsH6M1hmhgtEOnWmBS9B0zvL9lKB08kNe5kiDrkY+EmzmbNZJM8RkLu7aQnvmvmK
+MuCoXDCXdZ8LTICxo0De5Y/ZtyNvAgMBAAGjgfAwge0wCQYDVR0TBAIwADAdBgNV
+HQ4EFgQUoSHga/OmG32M8AUzYccdfUdtZpkwHwYDVR0jBBgwFoAUTmUNO5KzWks0
+dfspJspTa6FlFX4wCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMH4G
+A1UdEQR3MHWCCTEyNy4wLjAuMYINd3d3LjEyNy4wLjAuMYIheG4tLW44ajZkczUz
+bHd3a3JxaHYyOGEuMTI3LjAuMC4xghZ4bi0tbHZlLTZsYWQuMTI3LjAuMC4xgg53
+d3cyLjEyNy4wLjAuMYIOd3d3MS4xMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggEB
+AERHvmu41zR5rOemWl/oK3/JqQQa1XWn1j73mLPrN+19ljdEDHyt6YrGUGlWY5E/
+K7SJDPcDrmmxsNYAM6Z8g9tW0OnY/FO+rbg+PsTlIs3Q66eldfEDur1JMgsbty9W
+qJgUQ1jUIwV8uRMAhW5w5VpFH55KwAZykzgkndLZgpnIX8LG8vlTSeHLE3KqB+jz
+g6b0TCimlHzX5EKw3tbsDeKvL81ExLbLVH3XHzJNlzHHbhMUDqH4buS07hWD/X4p
+5HsGNdLjmm55qqGikWWYSEYbMzarJsJ2GJSC6sCgyp6xmF+YTdccibTpcg36t6nC
+acMK7vM+/ZCHyAwzMWnhwJk=
+-----END CERTIFICATE-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.key b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.key
new file mode 100644
index 0000000..d11d407
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqvVXRb5YIi49y
+vc5wLAj1KItMA7p4rdNeqSaHX0XOfW7MGPlzyzTg1lC8R1Fhky1PGMVhd55Oiz/r
+NvC5HKTIPnMOyP2aMswiKEYAyANKRkK7DQbADocbypG4hH3JOEGovvlGz5568lwS
+mJrrNP4YjuFN+AapQwAtu4vsrfk+noJbgzLwRy9Mqt7mgycMszsF2e7uGvLNOdgV
+OjqdYx5j7Xer2QKxY7GgYklKQ8WfzOMdf1SSn15K1yd4MsH6M1hmhgtEOnWmBS9B
+0zvL9lKB08kNe5kiDrkY+EmzmbNZJM8RkLu7aQnvmvmKMuCoXDCXdZ8LTICxo0De
+5Y/ZtyNvAgMBAAECggEAMRSCEdVIxKYRk0M6j4YpAQgpmq1LshsNsp1fJPTfJS9r
+ZSIcuwuD5MnWpXi+zRS4po8RViZDjIJ82kmNwziXqbRB8NMYC3ZktcDr9peIUpaF
+4i0pbpX+gp+laew0GF6iiTZ0g8V04hS/IN1bUxeFn1ubKoWMrKVE7OCHpX6RMk13
+v+ffdWVSjeKGufFUlN6OTCdnwgXsZrJPmj1CWWnyC2kzdKhLk0slEuYRpG5Yhzz2
+oKdivyGcoYmtnPSJa6CJEy6EPEwbecryMPuBwhBJIKQb1EvoWHzxoE9IAfLaGUFD
+xJlT9IbVTR5xZ0MQuXg6zzOoFoa2hFHI+foCFrmDuQKBgQDjQJ0O8SFHLRHEXmwK
+adJcI+2wJHWUtK6ArsM1dPrUZ4sDHbPNPxpZ48WWjEK6vDNfbaYpUHmvV37+kJkM
+P4uunqxkID9nEsctAJRjnKmMf41k0/YwokPraXY/57DsUpWZK39q09c34PqPneBW
+4KEy4j21MRJp9kBgy9QHkHcYRQKBgQDAVpmQBjQiifvi2g6t/kZndEdRnbC/zgNy
+82/1pNKdiewqlF9WsLem4KXSkvRuwlBeF4D1W6mDCwMNBn/YYN/GidmUS51Z96T1
+QFbRliBA/SKbEE9NZc5xlzLkgXHEDFfj3WIRIlgccWu//DcI2w3rfjRZag3GaG+X
+JX7CLZ2qIwKBgQCeNq7h5zjO7+7NsxsvCMuewJjLqCaAWGahSoq3nfC/jjL3AWfb
+vlIfQPegP5h5n2t4xcMIQnHlhFny60LShy89bFUDBHx/y1AF4cBttXVJTshm6Tce
+VupIbE0aYrkHXtuuHt1/x2qwCZ8H+9djRNKVtXNWwYpbYUki2uWMOqTfrQKBgQCk
+WtwKKGLwiLyHUxJsN/ZtfP1cjsV0gVSNK9ymKdwX5r26fOMjLwsgPKM2V4EsDDuk
+y6zU/SjS49wNi1o/yjgubalRgXPKZ0W8lOgbXI/fOPATVVKOrspEYpGIldxjTLDl
+9E1Smuh0Fa+fdKSKmrVAYK3XIjr3KlMA83dn8pbhxQKBgFGI8qh93jkhS+pWSj3u
+vJU7eML8DggzWMsY+lmTHWVAWM0o9LLcr3+LghgFdrerK2RjmGCZQ96YN+eB/XoI
+NQqQj39X5Yst5KhwQc+Iv1d2Wyu6cDECoXFJFL3eW0Cd4YQeaAMvAi6mpfNRAQWy
+UJOa4xQbckFK0V8JXHoVCF9P
+-----END PRIVATE KEY-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.pem b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.pem
new file mode 100644
index 0000000..bb883ca
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/127.0.0.1.pem
@@ -0,0 +1,85 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=web-platform-tests
+        Validity
+            Not Before: Oct 18 08:36:53 2016 GMT
+            Not After : Jan  4 08:36:53 2025 GMT
+        Subject: CN=127.0.0.1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:aa:bd:55:d1:6f:96:08:8b:8f:72:bd:ce:70:2c:
+                    08:f5:28:8b:4c:03:ba:78:ad:d3:5e:a9:26:87:5f:
+                    45:ce:7d:6e:cc:18:f9:73:cb:34:e0:d6:50:bc:47:
+                    51:61:93:2d:4f:18:c5:61:77:9e:4e:8b:3f:eb:36:
+                    f0:b9:1c:a4:c8:3e:73:0e:c8:fd:9a:32:cc:22:28:
+                    46:00:c8:03:4a:46:42:bb:0d:06:c0:0e:87:1b:ca:
+                    91:b8:84:7d:c9:38:41:a8:be:f9:46:cf:9e:7a:f2:
+                    5c:12:98:9a:eb:34:fe:18:8e:e1:4d:f8:06:a9:43:
+                    00:2d:bb:8b:ec:ad:f9:3e:9e:82:5b:83:32:f0:47:
+                    2f:4c:aa:de:e6:83:27:0c:b3:3b:05:d9:ee:ee:1a:
+                    f2:cd:39:d8:15:3a:3a:9d:63:1e:63:ed:77:ab:d9:
+                    02:b1:63:b1:a0:62:49:4a:43:c5:9f:cc:e3:1d:7f:
+                    54:92:9f:5e:4a:d7:27:78:32:c1:fa:33:58:66:86:
+                    0b:44:3a:75:a6:05:2f:41:d3:3b:cb:f6:52:81:d3:
+                    c9:0d:7b:99:22:0e:b9:18:f8:49:b3:99:b3:59:24:
+                    cf:11:90:bb:bb:69:09:ef:9a:f9:8a:32:e0:a8:5c:
+                    30:97:75:9f:0b:4c:80:b1:a3:40:de:e5:8f:d9:b7:
+                    23:6f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                A1:21:E0:6B:F3:A6:1B:7D:8C:F0:05:33:61:C7:1D:7D:47:6D:66:99
+            X509v3 Authority Key Identifier: 
+                keyid:4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+
+            X509v3 Key Usage: 
+                Digital Signature, Non Repudiation, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Subject Alternative Name: 
+                DNS:127.0.0.1, DNS:www.127.0.0.1, DNS:xn--n8j6ds53lwwkrqhv28a.127.0.0.1, DNS:xn--lve-6lad.127.0.0.1, DNS:www2.127.0.0.1, DNS:www1.127.0.0.1
+    Signature Algorithm: sha256WithRSAEncryption
+         44:47:be:6b:b8:d7:34:79:ac:e7:a6:5a:5f:e8:2b:7f:c9:a9:
+         04:1a:d5:75:a7:d6:3e:f7:98:b3:eb:37:ed:7d:96:37:44:0c:
+         7c:ad:e9:8a:c6:50:69:56:63:91:3f:2b:b4:89:0c:f7:03:ae:
+         69:b1:b0:d6:00:33:a6:7c:83:db:56:d0:e9:d8:fc:53:be:ad:
+         b8:3e:3e:c4:e5:22:cd:d0:eb:a7:a5:75:f1:03:ba:bd:49:32:
+         0b:1b:b7:2f:56:a8:98:14:43:58:d4:23:05:7c:b9:13:00:85:
+         6e:70:e5:5a:45:1f:9e:4a:c0:06:72:93:38:24:9d:d2:d9:82:
+         99:c8:5f:c2:c6:f2:f9:53:49:e1:cb:13:72:aa:07:e8:f3:83:
+         a6:f4:4c:28:a6:94:7c:d7:e4:42:b0:de:d6:ec:0d:e2:af:2f:
+         cd:44:c4:b6:cb:54:7d:d7:1f:32:4d:97:31:c7:6e:13:14:0e:
+         a1:f8:6e:e4:b4:ee:15:83:fd:7e:29:e4:7b:06:35:d2:e3:9a:
+         6e:79:aa:a1:a2:91:65:98:48:46:1b:33:36:ab:26:c2:76:18:
+         94:82:ea:c0:a0:ca:9e:b1:98:5f:98:4d:d7:1c:89:b4:e9:72:
+         0d:fa:b7:a9:c2:69:c3:0a:ee:f3:3e:fd:90:87:c8:0c:33:31:
+         69:e1:c0:99
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTYxMDE4MDgzNjUzWhcNMjUwMTA0MDgzNjUzWjAU
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCqvVXRb5YIi49yvc5wLAj1KItMA7p4rdNeqSaHX0XOfW7MGPlzyzTg1lC8
+R1Fhky1PGMVhd55Oiz/rNvC5HKTIPnMOyP2aMswiKEYAyANKRkK7DQbADocbypG4
+hH3JOEGovvlGz5568lwSmJrrNP4YjuFN+AapQwAtu4vsrfk+noJbgzLwRy9Mqt7m
+gycMszsF2e7uGvLNOdgVOjqdYx5j7Xer2QKxY7GgYklKQ8WfzOMdf1SSn15K1yd4
+MsH6M1hmhgtEOnWmBS9B0zvL9lKB08kNe5kiDrkY+EmzmbNZJM8RkLu7aQnvmvmK
+MuCoXDCXdZ8LTICxo0De5Y/ZtyNvAgMBAAGjgfAwge0wCQYDVR0TBAIwADAdBgNV
+HQ4EFgQUoSHga/OmG32M8AUzYccdfUdtZpkwHwYDVR0jBBgwFoAUTmUNO5KzWks0
+dfspJspTa6FlFX4wCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMH4G
+A1UdEQR3MHWCCTEyNy4wLjAuMYINd3d3LjEyNy4wLjAuMYIheG4tLW44ajZkczUz
+bHd3a3JxaHYyOGEuMTI3LjAuMC4xghZ4bi0tbHZlLTZsYWQuMTI3LjAuMC4xgg53
+d3cyLjEyNy4wLjAuMYIOd3d3MS4xMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggEB
+AERHvmu41zR5rOemWl/oK3/JqQQa1XWn1j73mLPrN+19ljdEDHyt6YrGUGlWY5E/
+K7SJDPcDrmmxsNYAM6Z8g9tW0OnY/FO+rbg+PsTlIs3Q66eldfEDur1JMgsbty9W
+qJgUQ1jUIwV8uRMAhW5w5VpFH55KwAZykzgkndLZgpnIX8LG8vlTSeHLE3KqB+jz
+g6b0TCimlHzX5EKw3tbsDeKvL81ExLbLVH3XHzJNlzHHbhMUDqH4buS07hWD/X4p
+5HsGNdLjmm55qqGikWWYSEYbMzarJsJ2GJSC6sCgyp6xmF+YTdccibTpcg36t6nC
+acMK7vM+/ZCHyAwzMWnhwJk=
+-----END CERTIFICATE-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cacert.pem b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cacert.pem
new file mode 100644
index 0000000..ff31326
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cacert.pem
@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=web-platform-tests
+        Validity
+            Not Before: Oct 18 08:36:53 2016 GMT
+            Not After : Jan  4 08:36:53 2025 GMT
+        Subject: CN=web-platform-tests
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c9:e3:69:85:18:ad:1a:df:ba:6c:05:96:0c:c1:
+                    22:51:78:3a:90:42:88:72:f7:f4:8c:81:59:43:5b:
+                    ea:13:0e:e7:ac:27:c2:72:55:c6:53:48:4f:08:37:
+                    a1:5f:e1:5c:78:24:d3:12:2d:2b:71:c3:51:a3:ad:
+                    0b:c6:a1:95:2d:f5:cf:db:50:64:c8:fa:d0:2a:bc:
+                    ae:a8:2b:44:53:a2:a9:e8:55:92:b7:95:4f:d2:f3:
+                    d9:f1:e1:0b:17:11:ad:62:50:c5:57:4d:43:ed:fc:
+                    6f:14:99:52:ab:27:f4:59:45:f8:86:77:d1:fc:00:
+                    8a:b3:4c:7e:66:1a:b3:04:58:e5:9e:57:52:b8:9d:
+                    ef:cc:ef:59:6e:a0:99:2c:e1:e1:1b:da:19:be:61:
+                    f9:c3:89:76:1e:88:1e:86:64:1f:5b:27:3e:44:7c:
+                    ad:d0:04:b0:57:db:79:bd:1e:7d:4a:73:52:c9:2d:
+                    16:6d:f0:80:2e:07:70:09:2f:ca:a2:c3:12:ae:2c:
+                    51:3d:d4:f3:4e:f6:53:c2:9d:e0:6c:56:6d:36:38:
+                    bc:1e:4f:d9:7e:1e:b9:26:fe:2f:f0:d6:6b:af:02:
+                    bf:4d:93:4b:a7:e7:bd:eb:e7:79:69:b1:65:1f:88:
+                    f0:fd:cc:03:ce:5e:00:83:b9:ad:70:6a:95:78:5b:
+                    97:fb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+            X509v3 Authority Key Identifier: 
+                keyid:4E:65:0D:3B:92:B3:5A:4B:34:75:FB:29:26:CA:53:6B:A1:65:15:7E
+                DirName:/CN=web-platform-tests
+                serial:01
+
+            X509v3 Key Usage: 
+                Certificate Sign
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+         c6:f0:d9:1e:24:82:3a:b8:d4:b3:92:00:ea:0e:d6:17:e9:8d:
+         a0:7a:35:bc:59:b9:2d:b0:6b:c2:27:74:66:6f:26:18:c2:75:
+         14:4c:29:76:3d:28:3c:6e:66:be:3c:27:55:17:09:f3:f4:2d:
+         1b:ab:b5:b5:5c:f6:5c:24:d1:7d:35:45:30:7e:41:19:79:f8:
+         a1:38:ca:ce:30:35:e0:de:1c:77:f8:fe:cd:dd:25:4c:13:18:
+         d7:9e:8b:35:04:58:24:7e:a3:f5:89:48:fc:28:e7:11:88:ec:
+         b8:37:e5:2f:8b:61:b9:f3:44:12:6a:62:cb:b3:16:5f:61:77:
+         8c:e6:d5:3f:ec:43:9a:22:7b:d2:e0:3a:25:15:49:e4:75:09:
+         a3:9e:82:64:a9:3f:94:86:c7:a8:87:a2:34:51:87:c9:58:89:
+         76:42:00:bc:7c:d3:17:7a:40:79:b1:f5:ac:0d:9d:14:a5:db:
+         57:7f:10:36:21:79:7e:8f:62:91:68:d2:1f:c9:d3:76:18:cf:
+         98:13:bf:21:a1:09:c3:40:17:9e:16:80:f4:61:23:46:cf:78:
+         9b:00:a7:fa:53:00:b5:67:d3:82:84:79:50:c0:ae:e0:0f:51:
+         55:84:e8:5a:cd:c7:6a:83:11:de:82:58:70:11:b8:1e:67:ab:
+         30:1d:a6:82
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjegAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTYxMDE4MDgzNjUzWhcNMjUwMTA0MDgzNjUzWjAd
+MRswGQYDVQQDDBJ3ZWItcGxhdGZvcm0tdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDJ42mFGK0a37psBZYMwSJReDqQQohy9/SMgVlDW+oTDues
+J8JyVcZTSE8IN6Ff4Vx4JNMSLStxw1GjrQvGoZUt9c/bUGTI+tAqvK6oK0RToqno
+VZK3lU/S89nx4QsXEa1iUMVXTUPt/G8UmVKrJ/RZRfiGd9H8AIqzTH5mGrMEWOWe
+V1K4ne/M71luoJks4eEb2hm+YfnDiXYeiB6GZB9bJz5EfK3QBLBX23m9Hn1Kc1LJ
+LRZt8IAuB3AJL8qiwxKuLFE91PNO9lPCneBsVm02OLweT9l+Hrkm/i/w1muvAr9N
+k0un573r53lpsWUfiPD9zAPOXgCDua1wapV4W5f7AgMBAAGjgZkwgZYwDAYDVR0T
+BAUwAwEB/zAdBgNVHQ4EFgQUTmUNO5KzWks0dfspJspTa6FlFX4wRQYDVR0jBD4w
+PIAUTmUNO5KzWks0dfspJspTa6FlFX6hIaQfMB0xGzAZBgNVBAMMEndlYi1wbGF0
+Zm9ybS10ZXN0c4IBATALBgNVHQ8EBAMCAgQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
+DQYJKoZIhvcNAQELBQADggEBAMbw2R4kgjq41LOSAOoO1hfpjaB6NbxZuS2wa8In
+dGZvJhjCdRRMKXY9KDxuZr48J1UXCfP0LRurtbVc9lwk0X01RTB+QRl5+KE4ys4w
+NeDeHHf4/s3dJUwTGNeeizUEWCR+o/WJSPwo5xGI7Lg35S+LYbnzRBJqYsuzFl9h
+d4zm1T/sQ5oie9LgOiUVSeR1CaOegmSpP5SGx6iHojRRh8lYiXZCALx80xd6QHmx
+9awNnRSl21d/EDYheX6PYpFo0h/J03YYz5gTvyGhCcNAF54WgPRhI0bPeJsAp/pT
+ALVn04KEeVDAruAPUVWE6FrNx2qDEd6CWHARuB5nqzAdpoI=
+-----END CERTIFICATE-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cakey.pem b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cakey.pem
new file mode 100644
index 0000000..7de82894
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/cakey.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIkWIO4EtVGkYCAggA
+MBQGCCqGSIb3DQMHBAi3OesMu9lDwgSCBMjDYQ4+dIkQmN4JbaHXINc8O3DU78J/
+ToUEkMz7t3G2aVRJk7C3Rbzk3u8OMs1p+3CaoH7fpF86kvDjeHwGygRPSZ/ZEh9W
+Si4zMd1xFVlfdbGf1/9n4nEGeorK8rGShXnhs/x1v9ZAmkl3xl9UUkNeRqfmgfor
+FefDcqWaLbGfyq2GVrUk6P2F9klfitdWvqoatKkUjLyO04r4cr7orpLI60gbO7tb
+Huis1C8rmcbAkI5alHZuvlJrAcFacV4oO9SXrIgYiYiLb+8vyGz2Ol6YaFUN+KXq
+K467P0K6y2BxWB6N8UbeQSwgyOA5ckS0kdFeDsBv8WR8eXpDPhLETYEDoNJcUpN2
+Je1hrRUgCDsllSK92NQt2atuXKdUBJCNQ+Y5X/8HsLDqDynm3ea1Sc5gj9a/H0OQ
+yGj5S+xSRQcLTHKANwvOoT1jgrnA5lJSYrbYMY9Wqdt2bJMgxbXsf3CRY0p2uf9m
+9AkYZzDZVocV1mGgtrSSIcFFvEYI8D10z6rRIhTcx/4k0mmyMWxVTW35JHyLvLQd
+nIRbGXYz9zgUYRiOGUDLbrYYZud13rE+62Y8AxVHbrUXMSala3jFN+Ly5JXnLL1P
+5Cy6Hab1BZ4WOBUHEOP5GhKmnrWGkRBSPhQVlOXW5ymTZoSwBgslovZZAhZU4yp0
+RvTldjygC499hTdAfQfrQYok0RnCfVqKIZkng8O5j6IbtPn7dyq4Zv/6sNdNwzPD
+EkkkbPpzvbrboOVf4dmLo8PBIRgJTKZoBBj/RSEVLwx2Zv9azJ0OuQEIY62a9039
+Ithl7HbQYSr8B0JJheL/dYdeICiz9sU97nAf/I9oXGeVj/rX5KwEbSIi10TFTKkA
+9v+tbmeHj8B/87xHexZ8NIMXDTYq2TY4PFlUjFIClNofVPrcJidpBfdyF5RPqfrw
+tH53Cc2sxN+woefPNQPoQHw8WUQ0MgxdN1XwuizvZKM3+44wqhsQloSNrzngjAJ4
+Y4A+oNkTqCuk0uonln3G8Ay2W5A0MKNSSmPXqUeGeANn2Lqj7UCVBeFwx0kjwKGF
+Bn1+xff/Uk09sy8SBlQRqoFkYak4QIrA1AznzQz860zHLmfoH0el8GSQQ26F2xT2
+ICZ0ma52tcAleTH3z6qLcAqh4ykKEMVoIun/ZwVYpqsucntCNQh35M5EUjb+9Tl7
+hGg3Ez8IZ2lkYlpYPzYEd5+vrl4AP/tGWOSmvb9LziulCn0WkZyroTCN7q66WEyo
+fqRIgk+5//NX3CSNKc+bVjNIxQvgh1xQX1zKwcWVbwLykTTkqIaiDsqDVteyOawh
+s1Zb253GSul0mCgixZG0nGciIYZpK7ky6PWC1SbQEKh8VlYgusqgJ7cMtAlb4Cgy
+p2WM/my+FN9DbzUYlgzE8XtWoYbf5NPk7Dx/iOp2mePMunhjc6TaM9T4fAsec7sf
+R8QHIeaQiP2P8ngnhgRe2iSWN1PzAq4ecs/WJ7w+Gpto+QWXy6nzEfcrL55uvIl3
+G2BANBvZnOoymYuZHGArlHT4xFISz3dFPuYzk56ux1riLMB2GKgjoSwU/Mkx5+Df
+dN/ECIt28jcJIWUHhg91FaWXLvmp1peGydi2CHyPzWR43szB2Z/mkrzz6vVomBXc
+Blo=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt
new file mode 100644
index 0000000..c51d519
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt
@@ -0,0 +1,2 @@
+V	250104083653Z		01	unknown	/CN=web-platform-tests
+V	250104083653Z		02	unknown	/CN=127.0.0.1
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr
new file mode 100644
index 0000000..8f7e63a
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr
@@ -0,0 +1 @@
+unique_subject = yes
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr.old b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr.old
new file mode 100644
index 0000000..8f7e63a
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.attr.old
@@ -0,0 +1 @@
+unique_subject = yes
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.old b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.old
new file mode 100644
index 0000000..d9c0e58
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/index.txt.old
@@ -0,0 +1 @@
+V	250104083653Z		01	unknown	/CN=web-platform-tests
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial
new file mode 100644
index 0000000..75016ea3
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial
@@ -0,0 +1 @@
+03
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial.old b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial.old
new file mode 100644
index 0000000..9e22bcb8
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/certs/serial.old
@@ -0,0 +1 @@
+02
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt.config.json b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt.config.json
index 84fd3f4c..6243954c 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt.config.json
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt.config.json
@@ -9,5 +9,13 @@
     "https": [8444],
     "ws": [9001],
     "wss": [9444]
+  },
+  "ssl": {
+    "type": "pregenerated",
+    "encrypt_after_connect": false,
+    "pregenerated": {
+      "host_key_path": "../certs/127.0.0.1.key",
+      "host_cert_path": "../certs/127.0.0.1.pem"
+    }
   }
 }
diff --git a/third_party/WebKit/public/platform/WebContentDecryptionModuleException.h b/third_party/WebKit/public/platform/WebContentDecryptionModuleException.h
index ca278ae7..8efa194 100644
--- a/third_party/WebKit/public/platform/WebContentDecryptionModuleException.h
+++ b/third_party/WebKit/public/platform/WebContentDecryptionModuleException.h
@@ -7,14 +7,16 @@
 
 namespace blink {
 
+// From https://w3c.github.io/encrypted-media/#exceptions.
 enum WebContentDecryptionModuleException {
+  WebContentDecryptionModuleExceptionTypeError,
   WebContentDecryptionModuleExceptionNotSupportedError,
   WebContentDecryptionModuleExceptionInvalidStateError,
-  WebContentDecryptionModuleExceptionInvalidAccessError,
   WebContentDecryptionModuleExceptionQuotaExceededError,
-  WebContentDecryptionModuleExceptionUnknownError,
-  WebContentDecryptionModuleExceptionClientError,
-  WebContentDecryptionModuleExceptionOutputError,
+  // TODO(jrummell): UnknownError is not part of the spec, but CDMs can
+  // generate other error codes (in addition to the 4 listed above). Remove
+  // UnknownError when the CDMs no longer use other error codes.
+  WebContentDecryptionModuleExceptionUnknownError
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/public/platform/modules/notifications/WebNotificationDelegate.h b/third_party/WebKit/public/platform/modules/notifications/WebNotificationDelegate.h
index 2ca0846d..319d0d3 100644
--- a/third_party/WebKit/public/platform/modules/notifications/WebNotificationDelegate.h
+++ b/third_party/WebKit/public/platform/modules/notifications/WebNotificationDelegate.h
@@ -7,16 +7,15 @@
 
 namespace blink {
 
-class WebString;
-
 // A delegate through which the embedder can trigger events on a Document-bound
 // Web Notifications object. Service Worker-bound Web Notifications will not
 // have a delegate, as their events will be fired on a Service Worker instead.
 class WebNotificationDelegate {
  public:
-  virtual void didShowNotification(const WebString& notificationId) = 0;
-  virtual void didClickNotification() = 0;
-  virtual void didCloseNotification() = 0;
+  virtual void dispatchClickEvent() = 0;
+  virtual void dispatchShowEvent() = 0;
+  virtual void dispatchErrorEvent() = 0;
+  virtual void dispatchCloseEvent() = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/public/platform/modules/notifications/WebNotificationManager.h b/third_party/WebKit/public/platform/modules/notifications/WebNotificationManager.h
index 3496f6e0..20aa454a 100644
--- a/third_party/WebKit/public/platform/modules/notifications/WebNotificationManager.h
+++ b/third_party/WebKit/public/platform/modules/notifications/WebNotificationManager.h
@@ -59,10 +59,13 @@
                                 WebServiceWorkerRegistration*,
                                 WebNotificationGetCallbacks*) = 0;
 
-  // Closes a notification identified by its notification Id.
-  virtual void close(const WebSecurityOrigin&,
-                     const WebString& tag,
-                     const WebString& notificationId) = 0;
+  // Closes a notification previously shown, and removes it if being shown.
+  virtual void close(WebNotificationDelegate*) = 0;
+
+  // Closes a persistent notification identified by its notification Id.
+  virtual void closePersistent(const WebSecurityOrigin&,
+                               const WebString& tag,
+                               const WebString& notificationId) = 0;
 
   // Indicates that the delegate object is being destroyed, and must no longer
   // be used by the embedder to dispatch events.
diff --git a/third_party/WebKit/public/web/WebSettings.h b/third_party/WebKit/public/web/WebSettings.h
index d390ab1b..764fc47 100644
--- a/third_party/WebKit/public/web/WebSettings.h
+++ b/third_party/WebKit/public/web/WebSettings.h
@@ -214,7 +214,6 @@
   virtual void setPerTilePaintingEnabled(bool) = 0;
   virtual void setPictographFontFamily(const WebString&,
                                        UScriptCode = USCRIPT_COMMON) = 0;
-  virtual void setPinchOverlayScrollbarThickness(int) = 0;
   virtual void setPluginsEnabled(bool) = 0;
   virtual void setAvailablePointerTypes(int) = 0;
   virtual void setPrimaryPointerType(PointerType) = 0;
diff --git a/third_party/closure_compiler/README.chromium b/third_party/closure_compiler/README.chromium
index fc7701c..65f0c48 100644
--- a/third_party/closure_compiler/README.chromium
+++ b/third_party/closure_compiler/README.chromium
@@ -3,7 +3,7 @@
 URL: http://github.com/google/closure-compiler
 Version: v20150729-236-gad656a1
 Date: 2015/08/26 08:46
-Revision: c64418fd5518bb60aeb6a627d2bcc46a3d4be788
+Revision: 6466a369c2e0ba3a7eede1e0347f89fd011ad1cf
 License: Apache 2.0
 License File: LICENSE
 Security Critical: no
diff --git a/third_party/closure_compiler/compiler/compiler.jar b/third_party/closure_compiler/compiler/compiler.jar
index 2b95321..2782453a 100644
--- a/third_party/closure_compiler/compiler/compiler.jar
+++ b/third_party/closure_compiler/compiler/compiler.jar
Binary files differ
diff --git a/third_party/closure_compiler/externs/chrome_extensions.js b/third_party/closure_compiler/externs/chrome_extensions.js
index 6c046f6..57d3ebd 100644
--- a/third_party/closure_compiler/externs/chrome_extensions.js
+++ b/third_party/closure_compiler/externs/chrome_extensions.js
@@ -321,12 +321,14 @@
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.focus = function() {};
 
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.fullscreen = function() {};
 
@@ -340,6 +342,7 @@
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.minimize = function() {};
 
@@ -353,6 +356,7 @@
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.maximize = function() {};
 
@@ -366,6 +370,7 @@
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.restore = function() {};
 
@@ -374,6 +379,7 @@
  * @param {number} left The new left position, in pixels.
  * @param {number} top The new top position, in pixels.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.moveTo = function(left, top) {};
 
@@ -382,24 +388,28 @@
  * @param {number} width The new width, in pixels.
  * @param {number} height The new height, in pixels.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.resizeTo = function(width, height) {};
 
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.drawAttention = function() {};
 
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.clearAttention = function() {};
 
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.close = function() {};
 
@@ -407,12 +417,14 @@
 /**
  * @param {boolean=} opt_focus Should the window be focused? Defaults to true.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.show = function(opt_focus) {};
 
 
 /**
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.hide = function() {};
 
@@ -427,6 +439,7 @@
 /**
  * @param {!chrome.app.window.ContentBounds} bounds The new window bounds.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.setBounds = function(bounds) {};
 
@@ -442,6 +455,7 @@
  * @param {boolean} alwaysOnTop Set whether the window should stay above most
  *     other windows.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.setAlwaysOnTop = function(alwaysOnTop) {};
 
@@ -450,6 +464,7 @@
  * @param {boolean} alwaysVisible Set whether the window is visible on all
  *     workspaces.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.setVisibleOnAllWorkspaces =
     function(alwaysVisible) {};
@@ -459,6 +474,7 @@
  * @param {boolean} wantAllKeys Set whether the window should get all keyboard
  *     events including system keys that are usually not sent.
  * @see http://developer.chrome.com/apps/app.window.html#type-AppWindow
+ * @return {undefined}
  */
 chrome.app.window.AppWindow.prototype.setInterceptAllKeys =
     function(wantAllKeys) {};
@@ -595,6 +611,7 @@
  * @param {function(!chrome.app.window.AppWindow)=} opt_createWindowCallback
  *     Callback to be run.
  * @see http://developer.chrome.com/apps/app.window.html#method-create
+ * @return {undefined}
  */
 chrome.app.window.create = function(
     url, opt_options, opt_createWindowCallback) {};
@@ -697,6 +714,7 @@
  * @param {!chrome.audioModem.RequestParams} params
  * @param {!ArrayBuffer} token
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.audioModem.transmit = function(params, token, callback) {};
 
@@ -704,6 +722,7 @@
 /**
  * @param {string} band
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.audioModem.stopTransmit = function(band, callback) {};
 
@@ -711,6 +730,7 @@
 /**
  * @param {!chrome.audioModem.RequestParams} params
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.audioModem.receive = function(params, callback) {};
 
@@ -718,6 +738,7 @@
 /**
  * @param {string} band
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.audioModem.stopReceive = function(band, callback) {};
 
@@ -728,12 +749,14 @@
 
 /**
  * @param {function(!Array<!chrome.audioModem.ReceivedToken>)} callback
+ * @return {undefined}
  */
 chrome.audioModem.ReceivedEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!Array<!chrome.audioModem.ReceivedToken>)} callback
+ * @return {undefined}
  */
 chrome.audioModem.ReceivedEvent.prototype.removeListener =
     function(callback) {};
@@ -842,13 +865,30 @@
 chrome.bluetooth.Device.prototype.connected;
 
 
+/** @type {boolean|undefined} */
+chrome.bluetooth.Device.prototype.connecting;
+
+
+/** @type {boolean|undefined} */
+chrome.bluetooth.Device.prototype.connectable;
+
+
 /** @type {!Array<string>|undefined} */
 chrome.bluetooth.Device.prototype.uuids;
 
 
+/** @type {number|undefined} */
+chrome.bluetooth.Device.prototype.inquiryRssi;
+
+
+/** @type {number|undefined} */
+chrome.bluetooth.Device.prototype.inquiryTxPower;
+
+
 /**
  * @param {function(!chrome.bluetooth.AdapterState)} callback
  * @see https://developer.chrome.com/apps/bluetooth#method-getAdapterState
+ * @return {undefined}
  */
 chrome.bluetooth.getAdapterState = function(callback) {};
 
@@ -857,6 +897,7 @@
  * @param {string} deviceAddress
  * @param {function(!chrome.bluetooth.Device)} callback
  * @see https://developer.chrome.com/apps/bluetooth#method-getDevice
+ * @return {undefined}
  */
 chrome.bluetooth.getDevice = function(deviceAddress, callback) {};
 
@@ -864,6 +905,7 @@
 /**
  * @param {function(!Array<!chrome.bluetooth.Device>)} callback
  * @see https://developer.chrome.com/apps/bluetooth#method-getDevices
+ * @return {undefined}
  */
 chrome.bluetooth.getDevices = function(callback) {};
 
@@ -871,6 +913,7 @@
 /**
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetooth#method-startDiscovery
+ * @return {undefined}
  */
 chrome.bluetooth.startDiscovery = function(opt_callback) {};
 
@@ -878,6 +921,7 @@
 /**
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetooth#method-stopDiscovery
+ * @return {undefined}
  */
 chrome.bluetooth.stopDiscovery = function(opt_callback) {};
 
@@ -890,12 +934,18 @@
 chrome.bluetooth.AdapterStateEvent = function() {};
 
 
-/** @param {function(!chrome.bluetooth.AdapterState): void} callback */
+/**
+ * @param {function(!chrome.bluetooth.AdapterState): void} callback
+ * @return {undefined}
+ */
 chrome.bluetooth.AdapterStateEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.bluetooth.AdapterState): void} callback */
+/**
+ * @param {function(!chrome.bluetooth.AdapterState): void} callback
+ * @return {undefined}
+ */
 chrome.bluetooth.AdapterStateEvent.prototype.removeListener =
     function(callback) {};
 
@@ -927,11 +977,17 @@
 chrome.bluetooth.DeviceEvent = function() {};
 
 
-/** @param {function(!chrome.bluetooth.Device): void} callback */
+/**
+ * @param {function(!chrome.bluetooth.Device): void} callback
+ * @return {undefined}
+ */
 chrome.bluetooth.DeviceEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(!chrome.bluetooth.Device): void} callback */
+/**
+ * @param {function(!chrome.bluetooth.Device): void} callback
+ * @return {undefined}
+ */
 chrome.bluetooth.DeviceEvent.prototype.removeListener = function(callback) {};
 
 
@@ -1041,6 +1097,7 @@
  *     function(!{socketId: number})} propertiesOrCallback
  * @param {function(!{socketId: number})=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-create
+ * @return {undefined}
  */
 chrome.bluetoothSocket.create = function(propertiesOrCallback, opt_callback) {};
 
@@ -1050,6 +1107,7 @@
  * @param {!chrome.bluetoothSocket.SocketProperties} properties
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-update
+ * @return {undefined}
  */
 chrome.bluetoothSocket.update = function(socketId, properties, opt_callback) {};
 
@@ -1059,6 +1117,7 @@
  * @param {boolean} paused
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-setPaused
+ * @return {undefined}
  */
 chrome.bluetoothSocket.setPaused = function(socketId, paused, opt_callback) {};
 
@@ -1069,6 +1128,7 @@
  * @param {!chrome.bluetoothSocket.ListenOptions|function()} optionsOrCallback
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-listenUsingRfcomm
+ * @return {undefined}
  */
 chrome.bluetoothSocket.listenUsingRfcomm =
     function(socketId, uuid, optionsOrCallback, opt_callback) {};
@@ -1080,6 +1140,7 @@
  * @param {!chrome.bluetoothSocket.ListenOptions|function()} optionsOrCallback
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-listenUsingL2cap
+ * @return {undefined}
  */
 chrome.bluetoothSocket.listenUsingL2cap =
     function(socketId, uuid, optionsOrCallback, opt_callback) {};
@@ -1091,6 +1152,7 @@
  * @param {string} uuid
  * @param {function()} callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-connect
+ * @return {undefined}
  */
 chrome.bluetoothSocket.connect = function(socketId, address, uuid, callback) {};
 
@@ -1099,6 +1161,7 @@
  * @param {number} socketId
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-disconnect
+ * @return {undefined}
  */
 chrome.bluetoothSocket.disconnect = function(socketId, opt_callback) {};
 
@@ -1107,6 +1170,7 @@
  * @param {number} socketId
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-close
+ * @return {undefined}
  */
 chrome.bluetoothSocket.close = function(socketId, opt_callback) {};
 
@@ -1116,6 +1180,7 @@
  * @param {!ArrayBuffer} data
  * @param {function(number)=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-send
+ * @return {undefined}
  */
 chrome.bluetoothSocket.send = function(socketId, data, opt_callback) {};
 
@@ -1124,6 +1189,7 @@
  * @param {number} socketId
  * @param {function(!chrome.bluetoothSocket.SocketInfo)} callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-getInfo
+ * @return {undefined}
  */
 chrome.bluetoothSocket.getInfo = function(socketId, callback) {};
 
@@ -1131,6 +1197,7 @@
 /**
  * @param {function(!Array<!chrome.bluetoothSocket.SocketInfo>)} callback
  * @see https://developer.chrome.com/apps/bluetoothSocket#method-getSockets
+ * @return {undefined}
  */
 chrome.bluetoothSocket.getSockets = function(callback) {};
 
@@ -1161,6 +1228,7 @@
 
 /**
  * @param {function(!chrome.bluetoothSocket.AcceptEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.AcceptEvent.prototype.addListener =
     function(callback) {};
@@ -1168,6 +1236,7 @@
 
 /**
  * @param {function(!chrome.bluetoothSocket.AcceptEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.AcceptEvent.prototype.removeListener =
     function(callback) {};
@@ -1220,6 +1289,7 @@
 /**
  * @param {function(
  *     !chrome.bluetoothSocket.AcceptErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.AcceptErrorEvent.prototype.addListener =
     function(callback) {};
@@ -1228,6 +1298,7 @@
 /**
  * @param {function(
  *     !chrome.bluetoothSocket.AcceptErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.AcceptErrorEvent.prototype.removeListener =
     function(callback) {};
@@ -1277,6 +1348,7 @@
 
 /**
  * @param {function(!chrome.bluetoothSocket.ReceiveEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.ReceiveEvent.prototype.addListener =
     function(callback) {};
@@ -1284,6 +1356,7 @@
 
 /**
  * @param {function(!chrome.bluetoothSocket.ReceiveEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.ReceiveEvent.prototype.removeListener =
     function(callback) {};
@@ -1336,6 +1409,7 @@
 /**
  * @param {function(
  *     !chrome.bluetoothSocket.ReceiveErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.ReceiveErrorEvent.prototype.addListener =
     function(callback) {};
@@ -1344,6 +1418,7 @@
 /**
  * @param {function(
  *     !chrome.bluetoothSocket.ReceiveErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.bluetoothSocket.ReceiveErrorEvent.prototype.removeListener =
     function(callback) {};
@@ -1460,6 +1535,7 @@
  *     propertiesOrCallback
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-connect
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.connect =
   function(deviceAddress, propertiesOrCallback, opt_callback) {};
@@ -1468,6 +1544,7 @@
  * @param {string} deviceAddress
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-disconnect
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.disconnect = function(deviceAddress, opt_callback) {};
 
@@ -1476,6 +1553,7 @@
  * @param {string} serviceId
  * @param {function(!chrome.bluetoothLowEnergy.Service)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getService
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getService = function(serviceId, callback) {};
 
@@ -1484,6 +1562,7 @@
  * @param {string} deviceAddress
  * @param {function(!Array<!chrome.bluetoothLowEnergy.Service>)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getServices
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getServices = function(deviceAddress, callback) {};
 
@@ -1492,6 +1571,7 @@
  * @param {string} characteristicId
  * @param {function(!chrome.bluetoothLowEnergy.Characteristic)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getCharacteristic
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getCharacteristic =
     function(characteristicId, callback) {};
@@ -1502,6 +1582,7 @@
  * @param {function(!Array<!chrome.bluetoothLowEnergy.Characteristic>)}
  * callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getCharacteristics
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getCharacteristics =
     function(serviceId, callback) {};
@@ -1511,6 +1592,7 @@
  * @param {string} serviceId
  * @param {function(!Array<!chrome.bluetoothLowEnergy.Service>)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getIncludedServices
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getIncludedServices =
   function(serviceId, callback) {};
@@ -1520,6 +1602,7 @@
  * @param {string} descriptorId
  * @param {function(!chrome.bluetoothLowEnergy.Descriptor)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getDescriptor
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getDescriptor = function(descriptorId, callback) {};
 
@@ -1528,6 +1611,7 @@
  * @param {string} characteristicId
  * @param {function(!Array<!chrome.bluetoothLowEnergy.Descriptor>)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-getDescriptors
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.getDescriptors =
   function(characteristicId, callback) {};
@@ -1537,6 +1621,7 @@
  * @param {string} characteristicId
  * @param {function(!chrome.bluetoothLowEnergy.Characteristic)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-readCharacteristicValue
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.readCharacteristicValue =
   function(characteristicId, callback) {};
@@ -1547,6 +1632,7 @@
  * @param {!ArrayBuffer} value
  * @param {function()} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-writeCharacteristicValue
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.writeCharacteristicValue =
   function(characteristicId, value, callback) {};
@@ -1565,7 +1651,8 @@
   *     propertiesOrCallback
   * @param {function()=} opt_callback
   * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-startCharacteristicNotifications
-  */
+  * @return {undefined}
+ */
 chrome.bluetoothLowEnergy.startCharacteristicNotifications =
   function(characteristicId, propertiesOrCallback, opt_callback) {};
 
@@ -1574,7 +1661,8 @@
   * @param {string} characteristicId
   * @param {function()=} opt_callback
   * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-stopCharacteristicNotifications
-  */
+  * @return {undefined}
+ */
 chrome.bluetoothLowEnergy.stopCharacteristicNotifications =
   function(characteristicId, opt_callback) {};
 
@@ -1583,6 +1671,7 @@
  * @param {string} descriptorId
  * @param {function(!chrome.bluetoothLowEnergy.Descriptor)} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-readDescriptorValue
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.readDescriptorValue =
   function(descriptorId, callback) {};
@@ -1593,6 +1682,7 @@
  * @param {!ArrayBuffer} value
  * @param {function()} callback
  * @see https://developer.chrome.com/apps/bluetoothLowEnergy#method-writeDescriptorValue
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.writeDescriptorValue =
   function(descriptorId, value, callback) {};
@@ -1605,12 +1695,18 @@
 chrome.bluetoothLowEnergy.ServiceEvent = function() {};
 
 
-/** @param {function(!chrome.bluetoothLowEnergy.Service): void} callback */
+/**
+ * @param {function(!chrome.bluetoothLowEnergy.Service): void} callback
+ * @return {undefined}
+ */
 chrome.bluetoothLowEnergy.ServiceEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.bluetoothLowEnergy.Service): void} callback */
+/**
+ * @param {function(!chrome.bluetoothLowEnergy.Service): void} callback
+ * @return {undefined}
+ */
 chrome.bluetoothLowEnergy.ServiceEvent.prototype.removeListener =
     function(callback) {};
 
@@ -1657,6 +1753,7 @@
 /**
  * @param {function(!chrome.bluetoothLowEnergy.Characteristic): void}
  *     callback
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.CharacteristicEvent.prototype.addListener =
     function(callback) {};
@@ -1665,6 +1762,7 @@
 /**
  * @param {function(!chrome.bluetoothLowEnergy.Characteristic): void}
  *     callback
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.CharacteristicEvent.prototype.removeListener =
     function(callback) {};
@@ -1701,6 +1799,7 @@
 /**
  * @param {function(!chrome.bluetoothLowEnergy.Descriptor): void}
  *     callback
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.DescriptorEvent.prototype.addListener =
     function(callback) {};
@@ -1709,6 +1808,7 @@
 /**
  * @param {function(!chrome.bluetoothLowEnergy.Descriptor): void}
  *     callback
+ * @return {undefined}
  */
 chrome.bluetoothLowEnergy.DescriptorEvent.prototype.removeListener =
     function(callback) {};
@@ -1743,6 +1843,7 @@
 
 /**
  * @param {function(Array<string>): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.commands.getAll = function(callback) {};
 
@@ -1852,6 +1953,7 @@
  * @param {!Array<!chrome.copresence.Operation>} operations
  * @param {function(string): void} callback
  * @see https://developer.chrome.com/apps/copresence#method-execute
+ * @return {undefined}
  */
 chrome.copresence.execute = function(operations, callback) {};
 
@@ -1868,6 +1970,7 @@
 
 /**
  * @param {function(string, !Array<!chrome.copresence.Message>): void} callback
+ * @return {undefined}
  */
 chrome.copresence.MessagesReceivedEvent.prototype.addListener =
     function(callback) {};
@@ -1875,6 +1978,7 @@
 
 /**
  * @param {function(string, !Array<!chrome.copresence.Message>): void} callback
+ * @return {undefined}
  */
 chrome.copresence.MessagesReceivedEvent.prototype.removeListener =
     function(callback) {};
@@ -1948,6 +2052,7 @@
  *     Web API.
  * @param {function(!ArrayBuffer): void=} callback Called back with the
  *     challenge response.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.challengeMachineKey =
     function(challenge, callback) {};
@@ -1963,6 +2068,7 @@
  *     function will then generate a new Enterprise User Key.
  * @param {function(!ArrayBuffer): void=} callback Called back with the
  *     challenge response.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.challengeUserKey =
     function(challenge, registerKey, callback) {};
@@ -1971,6 +2077,7 @@
 /**
  * @param {function(!Array<!chrome.enterprise.Token>): void} callback Called
  * with an array of Tokens.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.getTokens = function(callback) {};
 
@@ -1979,6 +2086,7 @@
  * @param {string} tokenId Id of cetificate token either "user" or "system".
  * @param {(function(!Array<!ArrayBuffer>): void)} callback Array of DER
  *     encoded x.509 certificates.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.getCertificates = function(tokenId, callback) {};
 
@@ -1988,6 +2096,7 @@
  * @param {!ArrayBuffer} certificate The DER encoding of a X.509 certificate.
  * @param {function(): void=} opt_callback Called back when this operation is
  *     finished.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.importCertificate =
     function(tokenId, certificate, opt_callback) {};
@@ -1998,6 +2107,7 @@
  * @param {!ArrayBuffer} certificate The DER encoding of a X.509 certificate.
  * @param {(function(): void)=} opt_callback Called back when this operation is
  *     finished.
+ * @return {undefined}
  */
 chrome.enterprise.platformKeys.removeCertificate =
     function(tokenId, certificate, opt_callback) {};
@@ -2010,6 +2120,16 @@
 chrome.extension = {};
 
 
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/extension#type-ViewType
+ */
+chrome.extension.ViewType = {
+  TAB: '',
+  POPUP: '',
+};
+
+
 /** @type {!Object|undefined} */
 chrome.extension.lastError = {};
 
@@ -2053,8 +2173,16 @@
 
 
 /**
- * @param {Object=} opt_fetchProperties An object with optional 'type' and
- *     optional 'windowId' keys.
+ * @typedef {?{
+ *   type: (!chrome.extension.ViewType|string|undefined),
+ *   windowId: (number|undefined),
+ *   tabId: (number|undefined)
+ * }}
+ */
+chrome.extension.ViewInfo;
+
+/**
+ * @param {?chrome.extension.ViewInfo=} opt_fetchProperties
  * @return {Array<Window>} The global JS objects for each content view.
  */
 chrome.extension.getViews = function(opt_fetchProperties) {};
@@ -2062,12 +2190,14 @@
 
 /**
  * @param {function(boolean): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.extension.isAllowedFileSchemeAccess = function(callback) {};
 
 
 /**
  * @param {function(boolean): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.extension.isAllowedIncognitoAccess = function(callback) {};
 
@@ -2079,6 +2209,7 @@
  * @param {*=} opt_request The request value, if arg1 was the extensionId.
  * @param {function(*): void=} opt_callback The callback function which
  *     takes a JSON response object sent by the handler of the request.
+ * @return {undefined}
  */
 chrome.extension.sendMessage = function(
     extensionIdOrRequest, opt_request, opt_callback) {};
@@ -2090,12 +2221,14 @@
  * @param {*=} opt_request The request value, if arg1 was the extensionId.
  * @param {function(*): void=} opt_callback The callback function which
  *     takes a JSON response object sent by the handler of the request.
+ * @return {undefined}
  */
 chrome.extension.sendRequest = function(opt_arg1, opt_request, opt_callback) {};
 
 
 /**
  * @param {string} data
+ * @return {undefined}
  */
 chrome.extension.setUpdateUrlData = function(data) {};
 
@@ -2127,12 +2260,14 @@
 
 /**
  * @param {function(!Window=): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.runtime.getBackgroundPage = function(callback) {};
 
 
 /**
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.runtime.openOptionsPage = function(opt_callback) {};
 
@@ -2207,12 +2342,14 @@
 /**
  * @param {string} url This may be used to clean up server-side data, do
  *     analytics, and implement surveys. Maximum 255 characters.
+ * @return {undefined}
  */
 chrome.runtime.setUninstallURL = function(url) {};
 
 
 /**
  * Reloads the app or extension.
+ * @return {undefined}
  */
 chrome.runtime.reload = function() {};
 
@@ -2221,6 +2358,7 @@
  * @param {function(string, !Object=): void} callback Called with "throttled",
  *     "no_update", or "update_available". If an update is available, the object
  *     contains more information about the available update.
+ * @return {undefined}
  */
 chrome.runtime.requestUpdateCheck = function(callback) {};
 
@@ -2228,6 +2366,7 @@
 /**
  * Restart the ChromeOS device when the app runs in kiosk mode. Otherwise, it's
  * no-op.
+ * @return {undefined}
  */
 chrome.runtime.restart = function() {};
 
@@ -2252,6 +2391,7 @@
  *     the native messaging host. If an error occurs while connecting to the
  *     native messaging host, the callback will be called with no arguments and
  *     chrome.runtime.lastError will be set to the error message.
+ * @return {undefined}
  */
 chrome.runtime.sendNativeMessage = function(
     application, message, opt_callback) {};
@@ -2260,12 +2400,14 @@
 /**
  *
  * @param {function(!Object)} callback
+ * @return {undefined}
  */
 chrome.runtime.getPlatformInfo = function(callback) {};
 
 
 /**
  * @param {function(!DirectoryEntry)} callback
+ * @return {undefined}
  */
 chrome.runtime.getPackageDirectoryEntry = function(callback) {};
 
@@ -2320,12 +2462,14 @@
 
 /**
  * @param {function(!Port): void} callback Callback.
+ * @return {undefined}
  */
 chrome.runtime.PortEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!Port): void} callback Callback.
+ * @return {undefined}
  */
 chrome.runtime.PortEvent.prototype.removeListener = function(callback) {};
 
@@ -2355,6 +2499,7 @@
 /**
  * @param {function(*, !MessageSender, function(*): void): (boolean|undefined)}
  *     callback Callback.
+ * @return {undefined}
  */
 chrome.runtime.MessageSenderEvent.prototype.addListener = function(callback) {};
 
@@ -2362,6 +2507,7 @@
 /**
  * @param {function(*, !MessageSender, function(*): void): (boolean|undefined)}
  *     callback Callback.
+ * @return {undefined}
  */
 chrome.runtime.MessageSenderEvent.prototype.removeListener = function(callback)
     {};
@@ -2389,6 +2535,16 @@
 
 
 /**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/tabs#type-TabStatus
+ */
+chrome.tabs.TabStatus = {
+  COMPLETE: '',
+  LOADING: '',
+};
+
+
+/**
  * @typedef {?{
  *   code: (string|undefined),
  *   file: (string|undefined),
@@ -2417,6 +2573,7 @@
  * @param {function(string):void=} opt_callback A callback function which
  *     accepts the data URL string of a JPEG encoding of the visible area of the
  *     captured tab.
+ * @return {undefined}
  */
 chrome.tabs.captureVisibleTab = function(windowIdOrOptionsOrCallback,
     opt_optionsOrCallback, opt_callback) {};
@@ -2425,6 +2582,7 @@
 /**
  * @param {number} tabId Tab Id.
  * @param {{name: (string|undefined)}=} connectInfo Info Object.
+ * @return {Port} New port.
  */
 chrome.tabs.connect = function(tabId, connectInfo) {};
 
@@ -2445,6 +2603,7 @@
 /**
  * @param {!chrome.tabs.CreateProperties} createProperties Info object.
  * @param {function(!Tab): void=} opt_callback The callback function.
+ * @return {undefined}
  */
 chrome.tabs.create = function(createProperties, opt_callback) {};
 
@@ -2457,6 +2616,7 @@
  * @param {function(string): void=} opt_callback An optional callback function
  *     that will be invoked with the language of the tab specified as first
  *     argument.
+ * @return {undefined}
  */
 chrome.tabs.detectLanguage = function(tabIdOrCallback, opt_callback) {};
 
@@ -2475,6 +2635,7 @@
  * @param {function(!Array<*>):void=} opt_callback A callback that will be
  *     invoked with the result of the execution of the script in every
  *     injected frame.
+ * @return {undefined}
  */
 chrome.tabs.executeScript = function(tabIdOrDetails, opt_detailsOrCallback,
     opt_callback) {};
@@ -2483,6 +2644,7 @@
 /**
  * @param {number} tabId Tab id.
  * @param {function(!Tab): void} callback Callback.
+ * @return {undefined}
  */
 chrome.tabs.get = function(tabId, callback) {};
 
@@ -2495,12 +2657,14 @@
  * @param {number?} windowId Window id.
  * @param {function(Array<Tab>): void} callback Callback.
  * @deprecated Please use tabs.query {windowId: windowId}.
+ * @return {undefined}
  */
 chrome.tabs.getAllInWindow = function(windowId, callback) {};
 
 
 /**
  * @param {function(!Tab=): void} callback Callback.
+ * @return {undefined}
  */
 chrome.tabs.getCurrent = function(callback) {};
 
@@ -2513,6 +2677,7 @@
  * @param {number?} windowId Window id.
  * @param {function(Tab): void} callback Callback.
  * @deprecated Please use tabs.query({active: true}).
+ * @return {undefined}
  */
 chrome.tabs.getSelected = function(windowId, callback) {};
 
@@ -2530,6 +2695,7 @@
  * @param {!chrome.tabs.HighlightInfo} highlightInfo
  * @param {function(!Window): void} callback Callback function invoked
  *    with each appropriate Window.
+ * @return {undefined}
  */
 chrome.tabs.highlight = function(highlightInfo, callback) {};
 
@@ -2546,6 +2712,7 @@
  *     callback that will be invoked after the CSS has been injected.
  * @param {function():void=} opt_callback A callback that will be invoked after
  *     the CSS has been injected.
+ * @return {undefined}
  */
 chrome.tabs.insertCSS = function(tabIdOrDetails, opt_detailsOrCallback,
     opt_callback) {};
@@ -2564,6 +2731,7 @@
  * @param {number|!Array<number>} tabId Tab id or array of tab ids.
  * @param {!chrome.tabs.MoveProperties} moveProperties
  * @param {function((!Tab|!Array<!Tab>)): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.tabs.move = function(tabId, moveProperties, opt_callback) {};
 
@@ -2575,7 +2743,7 @@
  *   highlighted: (boolean|undefined),
  *   currentWindow: (boolean|undefined),
  *   lastFocusedWindow: (boolean|undefined),
- *   status: (string|undefined),
+ *   status: (!chrome.tabs.TabStatus|string|undefined),
  *   title: (string|undefined),
  *   url: (!Array<string>|string|undefined),
  *   windowId: (number|undefined),
@@ -2589,6 +2757,7 @@
 /**
  * @param {!chrome.tabs.QueryInfo} queryInfo
  * @param {function(!Array<!Tab>): void} callback Callback.
+ * @return {undefined}
  */
 chrome.tabs.query = function(queryInfo, callback) {};
 
@@ -2598,6 +2767,7 @@
  * @param {number} tabId The ID of the tab which is to be duplicated.
  * @param {(function(!Tab=):void)=} opt_callback A callback to be invoked with
  *     details about the duplicated tab.
+ * @return {undefined}
  */
 chrome.tabs.duplicate = function(tabId, opt_callback) {};
 
@@ -2624,6 +2794,7 @@
  *     reload is complete, if no object needs to be specified.
  * @param {function():void=} opt_callback  A callback to be invoked when the
  *     reload is complete.
+ * @return {undefined}
  */
 chrome.tabs.reload = function(opt_tabIdOrReloadPropertiesOrCallback,
     opt_reloadPropertiesOrCallback, opt_callback) {};
@@ -2632,6 +2803,7 @@
 /**
  * @param {number|!Array<number>} tabIds A tab ID or an array of tab IDs.
  * @param {function(): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.tabs.remove = function(tabIds, opt_callback) {};
 
@@ -2652,6 +2824,7 @@
  *     callback function.
  * @param {function(*): void=} opt_callback The callback function which
  *     takes a JSON response object sent by the handler of the request.
+ * @return {undefined}
  */
 chrome.tabs.sendMessage = function(tabId, request, opt_optionsOrCallback,
     opt_callback) {};
@@ -2663,6 +2836,7 @@
  * @param {function(*): void=} opt_callback The callback function which
  *     takes a JSON response object sent by the handler of the request.
  * @deprecated Please use runtime.sendMessage.
+ * @return {undefined}
  */
 chrome.tabs.sendRequest = function(tabId, request, opt_callback) {};
 
@@ -2691,6 +2865,7 @@
  *     that will be invoked with information about the tab being updated.
  * @param {function(!Tab=): void=} opt_callback An optional callback that will
  *     be invoked with information about the tab being updated.
+ * @return {undefined}
  */
 chrome.tabs.update = function(tabIdOrUpdateProperties,
     opt_updatePropertiesOrCallback, opt_callback) {};
@@ -2786,6 +2961,7 @@
  * @param {function(!Array<!chrome.topSites.MostVisitedURL>)} callback Invoked
  *     with a list of most visited URLs.
  * @see https://developer.chrome.com/extensions/topSites#method-get
+ * @return {undefined}
  */
 chrome.topSites.get = function(callback) {};
 
@@ -2801,6 +2977,7 @@
  * @param {Object=} opt_createData May have many keys to specify parameters.
  *     Or the callback.
  * @param {function(ChromeWindow): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.create = function(opt_createData, opt_callback) {};
 
@@ -2810,6 +2987,7 @@
  * @param {Object=} opt_getInfo May have 'populate' key. Or the callback.
  * @param {function(!ChromeWindow): void=} opt_callback Callback when
  *     opt_getInfo is an object.
+ * @return {undefined}
  */
 chrome.windows.get = function(id, opt_getInfo, opt_callback) {};
 
@@ -2817,6 +2995,7 @@
 /**
  * @param {Object=} opt_getInfo May have 'populate' key. Or the callback.
  * @param {function(!Array<!ChromeWindow>): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.getAll = function(opt_getInfo, opt_callback) {};
 
@@ -2824,6 +3003,7 @@
 /**
  * @param {Object=} opt_getInfo May have 'populate' key. Or the callback.
  * @param {function(ChromeWindow): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.getCurrent = function(opt_getInfo, opt_callback) { };
 
@@ -2831,6 +3011,7 @@
 /**
  * @param {Object=} opt_getInfo May have 'populate' key. Or the callback.
  * @param {function(ChromeWindow): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.getLastFocused = function(opt_getInfo, opt_callback) { };
 
@@ -2838,6 +3019,7 @@
 /**
  * @param {number} tabId Tab Id.
  * @param {function(): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.remove = function(tabId, opt_callback) {};
 
@@ -2847,6 +3029,7 @@
  * @param {Object} updateProperties An object which may have many keys for
  *     various options.
  * @param {function(): void=} opt_callback Callback.
+ * @return {undefined}
  */
 chrome.windows.update = function(tabId, updateProperties, opt_callback) {};
 
@@ -2888,6 +3071,7 @@
  * @param {function(Array<string>): void} callback The callback function which
  *     accepts an array of the accept languages of the browser, such as
  *     'en-US','en','zh-CN'.
+ * @return {undefined}
  */
 chrome.i18n.getAcceptLanguages = function(callback) {};
 
@@ -2910,6 +3094,7 @@
  * @param {string} text User input string to be detected.
  * @param {function(!Object)} callback The callback for passing back the
  *     language detection result.
+ * @return {undefined}
  */
 chrome.i18n.detectLanguage = function(text, callback) {};
 
@@ -2923,6 +3108,7 @@
 
 /**
  * @param {number} tabId Tab Id.
+ * @return {undefined}
  */
 chrome.pageAction.hide = function(tabId) {};
 
@@ -2930,24 +3116,28 @@
 /**
  * @param {Object} details An object which has 'tabId' and either
  *     'imageData' or 'path'.
+ * @return {undefined}
  */
 chrome.pageAction.setIcon = function(details) {};
 
 
 /**
  * @param {Object} details An object which may have 'popup' or 'tabId' as keys.
+ * @return {undefined}
  */
 chrome.pageAction.setPopup = function(details) {};
 
 
 /**
  * @param {Object} details An object which has 'tabId' and 'title'.
+ * @return {undefined}
  */
 chrome.pageAction.setTitle = function(details) {};
 
 
 /**
  * @param {number} tabId Tab Id.
+ * @return {undefined}
  */
 chrome.pageAction.show = function(tabId) {};
 
@@ -2968,6 +3158,7 @@
  * @param {(function(): void)=} opt_callback The callback function. If an error
  * occurs opening the URL, chrome.runtime.lastError will be set to the error
  * message.
+ * @return {undefined}
  */
 chrome.browser.openTab = function(details, opt_callback) {};
 
@@ -3010,6 +3201,7 @@
  *   tabId: (number|undefined)
  * }} details
  * @see https://developer.chrome.com/extensions/browserAction#method-setTitle
+ * @return {undefined}
  */
 chrome.browserAction.setTitle = function(details) {};
 
@@ -3018,6 +3210,7 @@
  * @param {!chrome.browserAction.Tab} details
  * @param {function(string): void} callback
  * @see https://developer.chrome.com/extensions/browserAction#method-getTitle
+ * @return {undefined}
  */
 chrome.browserAction.getTitle = function(details, callback) {};
 
@@ -3026,6 +3219,7 @@
  * @param {!chrome.browserAction.SetIconImageData} details
  * @param {function(): void=} opt_callback
  * @see https://developer.chrome.com/extensions/browserAction#method-setIcon
+ * @return {undefined}
  */
 chrome.browserAction.setIcon = function(details, opt_callback) {};
 
@@ -3036,6 +3230,7 @@
  *   popup: string
  * }} details
  * @see https://developer.chrome.com/extensions/browserAction#method-setPopup
+ * @return {undefined}
  */
 chrome.browserAction.setPopup = function(details) {};
 
@@ -3044,6 +3239,7 @@
  * @param {!chrome.browserAction.Tab} details
  * @param {function(string): void} callback
  * @see https://developer.chrome.com/extensions/browserAction#method-getPopup
+ * @return {undefined}
  */
 chrome.browserAction.getPopup = function(details, callback) {};
 
@@ -3054,6 +3250,7 @@
  *   tabId: (number|undefined)
  * }} details
  * @see https://developer.chrome.com/extensions/browserAction#method-setBadgeText
+ * @return {undefined}
  */
 chrome.browserAction.setBadgeText = function(details) {};
 
@@ -3062,6 +3259,7 @@
  * @param {!chrome.browserAction.Tab} details
  * @param {function(string): void} callback
  * @see https://developer.chrome.com/extensions/browserAction#method-getBadgeText
+ * @return {undefined}
  */
 chrome.browserAction.getBadgeText = function(details, callback) {};
 
@@ -3072,6 +3270,7 @@
  *   tabId: (number|undefined)
  * }} details
  * @see https://developer.chrome.com/extensions/browserAction#method-setBadgeBackgroundColor
+ * @return {undefined}
  */
 chrome.browserAction.setBadgeBackgroundColor = function(details) {};
 
@@ -3080,6 +3279,7 @@
  * @param {!chrome.browserAction.Tab} details
  * @param {function(chrome.browserAction.ColorArray): void} callback
  * @see https://developer.chrome.com/extensions/browserAction#method-getBadgeBackgroundColor
+ * @return {undefined}
  */
 chrome.browserAction.getBadgeBackgroundColor = function(details, callback) {};
 
@@ -3087,6 +3287,7 @@
 /**
  * @param {number=} opt_tabId
  * @see https://developer.chrome.com/extensions/browserAction#method-enable
+ * @return {undefined}
  */
 chrome.browserAction.enable = function(opt_tabId) {};
 
@@ -3094,6 +3295,7 @@
 /**
  * @param {number=} opt_tabId
  * @see https://developer.chrome.com/extensions/browserAction#method-disable
+ * @return {undefined}
  */
 chrome.browserAction.disable = function(opt_tabId) {};
 
@@ -3106,6 +3308,7 @@
 
 /**
  * @param {function(!Tab): void} callback
+ * @return {undefined}
  */
 chrome.browserAction.BrowserActionTabEvent.prototype.addListener =
     function(callback) {};
@@ -3113,6 +3316,7 @@
 
 /**
  * @param {function(!Tab): void} callback
+ * @return {undefined}
  */
 chrome.browserAction.BrowserActionTabEvent.prototype.removeListener =
     function(callback) {};
@@ -3224,6 +3428,7 @@
  * @param {chrome.bookmarks.CreateDetails} bookmark
  * @param {function(BookmarkTreeNode): void=} opt_callback The
  *     callback function which accepts a BookmarkTreeNode object.
+ * @return {undefined}
  */
 chrome.bookmarks.create = function(bookmark, opt_callback) {};
 
@@ -3234,6 +3439,7 @@
  *     optional 'index'.
  * @param {function(BookmarkTreeNode): void=} opt_callback
  *     The callback function which accepts a BookmarkTreeNode object.
+ * @return {undefined}
  */
 chrome.bookmarks.move = function(id, destination, opt_callback) {};
 
@@ -3243,6 +3449,7 @@
  * @param {Object} changes An object which may have 'title' as a key.
  * @param {function(BookmarkTreeNode): void=} opt_callback The
  *     callback function which accepts a BookmarkTreeNode object.
+ * @return {undefined}
  */
 chrome.bookmarks.update = function(id, changes, opt_callback) {};
 
@@ -3250,6 +3457,7 @@
 /**
  * @param {string} id
  * @param {function(): void=} opt_callback
+ * @return {undefined}
  */
 chrome.bookmarks.remove = function(id, opt_callback) {};
 
@@ -3257,18 +3465,21 @@
 /**
  * @param {string} id
  * @param {function(): void=} opt_callback
+ * @return {undefined}
  */
 chrome.bookmarks.removeTree = function(id, opt_callback) {};
 
 
 /**
  * @param {function(): void=} opt_callback
+ * @return {undefined}
  */
 chrome.bookmarks.import = function(opt_callback) {};
 
 
 /**
  * @param {function(): void=} opt_callback
+ * @return {undefined}
  */
 chrome.bookmarks.export = function(opt_callback) {};
 
@@ -3324,12 +3535,14 @@
 
 /**
  * @param {function(string, function(!Array<!SuggestResult>)): void} callback
+ * @return {undefined}
  */
 chrome.omnibox.InputChangedEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(string, function(!Array<!SuggestResult>)): void} callback
+ * @return {undefined}
  */
 chrome.omnibox.InputChangedEvent.prototype.removeListener =
     function(callback) {};
@@ -3351,11 +3564,17 @@
 chrome.omnibox.InputEnteredEvent = function() {};
 
 
-/** @param {function(string, string): void} callback */
+/**
+ * @param {function(string, string): void} callback
+ * @return {undefined}
+ */
 chrome.omnibox.InputEnteredEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(string, string): void} callback */
+/**
+ * @param {function(string, string): void} callback
+ * @return {undefined}
+ */
 chrome.omnibox.InputEnteredEvent.prototype.removeListener =
     function(callback) {};
 
@@ -3373,6 +3592,7 @@
 
 /**
  * @param {{description: string}} suggestion A partial SuggestResult object.
+ * @return {undefined}
  */
 chrome.omnibox.setDefaultSuggestion = function(suggestion) {};
 
@@ -3449,6 +3669,7 @@
  * @param {!chrome.contextMenus.UpdateProperties} updateProperties
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/extensions/contextMenus#method-update
+ * @return {undefined}
  */
 chrome.contextMenus.update = function(id, updateProperties, opt_callback) {};
 
@@ -3457,6 +3678,7 @@
  * @param {(number|string)} menuItemId
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/extensions/contextMenus#method-remove
+ * @return {undefined}
  */
 chrome.contextMenus.remove = function(menuItemId, opt_callback) {};
 
@@ -3464,6 +3686,7 @@
 /**
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/extensions/contextMenus#method-removeAll
+ * @return {undefined}
  */
 chrome.contextMenus.removeAll = function(opt_callback) {};
 
@@ -3477,12 +3700,14 @@
 
 /**
  * @param {function(!Object, !Tab=)} callback
+ * @return {undefined}
  */
 chrome.contextMenus.ClickedEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!Object, !Tab=)} callback
+ * @return {undefined}
  */
 chrome.contextMenus.ClickedEvent.prototype.removeListener =
     function(callback) {};
@@ -3534,6 +3759,7 @@
 /**
  * @param {!chrome.cookies.CookieIdentifier} details
  * @param {function(Cookie=): void} callback
+ * @return {undefined}
  */
 chrome.cookies.get = function(details, callback) {};
 
@@ -3541,12 +3767,14 @@
 /**
  * @param {Object} details
  * @param {function(Array<Cookie>): void} callback
+ * @return {undefined}
  */
 chrome.cookies.getAll = function(details, callback) {};
 
 
 /**
  * @param {function(Array<CookieStore>): void} callback
+ * @return {undefined}
  */
 chrome.cookies.getAllCookieStores = function(callback) {};
 
@@ -3556,6 +3784,7 @@
  * @param {function(chrome.cookies.CookieIdentifier): void=} opt_callback If
  *     removal failed for any reason, the parameter will be "null", and
  *     "chrome.runtime.lastError" will be set.
+ * @return {undefined}
  */
 chrome.cookies.remove = function(details, opt_callback) {};
 
@@ -3581,6 +3810,7 @@
  * @param {function(Cookie): void=} opt_callback If setting failed for any
  *    reason, the parameter will be "null", and "chrome.runtime.lastError" will
  *    be set.
+ * @return {undefined}
  */
 chrome.cookies.set = function(details, opt_callback) {};
 
@@ -3625,6 +3855,7 @@
  * @param {string} id
  * @param {function(!ExtensionInfo): void=} opt_callback Optional callback
  *     function.
+ * @return {undefined}
  */
 chrome.management.get = function(id, opt_callback) {};
 
@@ -3640,6 +3871,7 @@
 /**
  * @param {string} id The id of an already installed extension.
  * @param {function(!Array<string>)=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.getPermissionWarningsById = function(id, opt_callback) {};
 
@@ -3647,6 +3879,7 @@
 /**
  * @param {string} manifestStr Extension's manifest JSON string.
  * @param {function(!Array<string>)=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.getPermissionWarningsByManifest =
     function(manifestStr, opt_callback) {};
@@ -3655,6 +3888,7 @@
 /**
  * @param {string} id The id of an already installed extension.
  * @param {function(): void=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.launchApp = function(id, opt_callback) {};
 
@@ -3663,6 +3897,7 @@
  * @param {string} id The id of an already installed extension.
  * @param {boolean} enabled Whether this item should be enabled.
  * @param {function(): void=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.setEnabled = function(id, enabled, opt_callback) {};
 
@@ -3673,6 +3908,7 @@
  *     opt_optionsOrCallback An optional uninstall options object or an optional
  *     callback function.
  * @param {function(): void=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.uninstall =
     function(id, opt_optionsOrCallback, opt_callback) {};
@@ -3683,6 +3919,7 @@
  *     opt_optionsOrCallback An optional uninstall options object or an optional
  *     callback function.
  * @param {function(): void=} opt_callback An optional callback function.
+ * @return {undefined}
  */
 chrome.management.uninstallSelf =
     function(opt_optionsOrCallback, opt_callback) {};
@@ -3691,6 +3928,7 @@
 /**
  * @param {string} id The id of an already installed extension.
  * @param {function(): void=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.createAppShortcut = function(id, opt_callback) {};
 
@@ -3701,6 +3939,7 @@
  *     value is in ExtensionInfo.availableLaunchTypes because the available
  *     launch types vary on different platforms and configurations.
  * @param {function(): void=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.management.setLaunchType = function(id, launchType, opt_callback) {};
 
@@ -3711,6 +3950,7 @@
  * @param {string} title The title of the generated app.
  * @param {function(!ExtensionInfo): void=} opt_callback Optional callback
  *     function.
+ * @return {undefined}
  */
 chrome.management.generateAppForLink = function(url, title, opt_callback) {};
 
@@ -3742,6 +3982,7 @@
  * @param {number} thresholdSeconds Threshold in seconds, used to determine
  *     when a machine is in the idle state.
  * @param {function(string): void} callback Callback to handle the state.
+ * @return {undefined}
  */
 chrome.idle.queryState = function(thresholdSeconds, callback) {};
 
@@ -3749,6 +3990,7 @@
 /**
  * @param {number} intervalInSeconds Threshold, in seconds, used to determine
  *    when the system is in an idle state.
+ * @return {undefined}
  */
 chrome.idle.setDetectionInterval = function(intervalInSeconds) {};
 
@@ -3817,6 +4059,7 @@
  * Gets an array of all available voices.
  * @param {function(Array<TtsVoice>)=} opt_callback An optional callback
  *     function.
+ * @return {undefined}
  */
 chrome.tts.getVoices = function(opt_callback) {};
 
@@ -3824,6 +4067,7 @@
 /**
  * Checks if the engine is currently speaking.
  * @param {function(boolean)=} opt_callback The callback function.
+ * @return {undefined}
  */
 chrome.tts.isSpeaking = function(opt_callback) {};
 
@@ -3836,12 +4080,14 @@
  *     32,768 characters.
  * @param {Object=} opt_options The speech options.
  * @param {function()=} opt_callback Called right away, before speech finishes.
+ * @return {undefined}
  */
 chrome.tts.speak = function(utterance, opt_options, opt_callback) {};
 
 
 /**
  * Stops any current speech.
+ * @return {undefined}
  */
 chrome.tts.stop = function() {};
 
@@ -3914,6 +4160,7 @@
  * @param {!chrome.fileBrowserHandler.SelectFileSelectionParams} selectionParams
  *     Parameters that will be used while selecting the file.
  * @param {function(!Object)} callback Function called upon completion.
+ * @return {undefined}
  */
 chrome.fileBrowserHandler.selectFile = function(selectionParams, callback) {};
 
@@ -3927,6 +4174,7 @@
 
 /**
  * @param {function(string, !FileHandlerExecuteEventDetails)} callback
+ * @return {undefined}
  */
 chrome.fileBrowserHandler.ExecuteEvent.prototype.addListener = function(
     callback) {};
@@ -3934,6 +4182,7 @@
 
 /**
  * @param {function(string, !FileHandlerExecuteEventDetails)} callback
+ * @return {undefined}
  */
 chrome.fileBrowserHandler.ExecuteEvent.prototype.removeListener = function(
     callback) {};
@@ -3984,6 +4233,7 @@
  *     send messages to the application.
  * @param {function(string): void} callback Function called when
  *     registration completes with registration ID as argument.
+ * @return {undefined}
  */
 chrome.gcm.register = function(senderIds, callback) {};
 
@@ -3992,6 +4242,7 @@
  * Unregisters the application from GCM.
  * @see https://developer.chrome.com/extensions/gcm#method-unregister
  * @param {function(): void} callback Called when unregistration is done.
+ * @return {undefined}
  */
 chrome.gcm.unregister = function(callback) {};
 
@@ -4001,6 +4252,7 @@
  * @see https://developer.chrome.com/extensions/gcm#method-send
  * @param {!chrome.gcm.Message} message Message to be sent.
  * @param {function(string): void} callback Called with message ID.
+ * @return {undefined}
  */
 chrome.gcm.send = function(message, callback) {};
 
@@ -4051,12 +4303,14 @@
 
 /**
  * @param {function(!Object): void} callback Callback.
+ * @return {undefined}
  */
 chrome.gcm.OnMessageEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!Object): void} callback Callback.
+ * @return {undefined}
  */
 chrome.gcm.OnMessageEvent.prototype.removeListener = function(callback) {};
 
@@ -4083,12 +4337,14 @@
 
 /**
  * @param {function(!Object): void} callback Callback.
+ * @return {undefined}
  */
 chrome.gcm.OnSendErrorEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!Object): void} callback Callback.
+ * @return {undefined}
  */
 chrome.gcm.OnSendErrorEvent.prototype.removeListener = function(callback) {};
 
@@ -4115,12 +4371,14 @@
 
 /**
  * @param {Object<string, string>} details Object with a 'url' key.
+ * @return {undefined}
  */
 chrome.history.addUrl = function(details) {};
 
 
 /**
  * @param {function(): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.history.deleteAll = function(callback) {};
 
@@ -4129,12 +4387,14 @@
  * @param {Object<string, string>} range Object with 'startTime'
  *     and 'endTime' keys.
  * @param {function(): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.history.deleteRange = function(range, callback) {};
 
 
 /**
  * @param {Object<string, string>} details Object with a 'url' key.
+ * @return {undefined}
  */
 chrome.history.deleteUrl = function(details) {};
 
@@ -4178,6 +4438,7 @@
  *     specified.
  * @param {function(string=): void=} opt_callback A callback function if options
  *     are specified.
+ * @return {undefined}
  */
 chrome.identity.getAuthToken = function(detailsOrCallback, opt_callback) {};
 
@@ -4189,6 +4450,7 @@
 /**
  * @param {chrome.identity.InvalidTokenDetails} details
  * @param {function(): void} callback
+ * @return {undefined}
  */
 chrome.identity.removeCachedAuthToken = function(details, callback) {};
 
@@ -4200,6 +4462,7 @@
 /**
  * @param {chrome.identity.WebAuthFlowDetails} details
  * @param {function(string=): void} callback
+ * @return {undefined}
  */
 chrome.identity.launchWebAuthFlow = function(details, callback) {};
 
@@ -4208,7 +4471,10 @@
 chrome.identity.WebAuthFlowDetails;
 
 
-/** @param {function(!Object):void} callback */
+/**
+ * @param {function(!Object):void} callback
+ * @return {undefined}
+ */
 chrome.identity.getProfileUserInfo = function(callback) {};
 
 
@@ -4239,6 +4505,7 @@
  * @param {function(string, !ChromeKeyboardEvent): (boolean|undefined)} callback
  *     callback.
  * @param {Array<string>=} opt_extraInfoSpec Array of extra information.
+ * @return {undefined}
  */
 ChromeInputImeOnKeyEventEvent.prototype.addListener =
     function(callback, opt_extraInfoSpec) {};
@@ -4247,6 +4514,7 @@
 /**
  * @param {function(string, !ChromeKeyboardEvent): (boolean|undefined)} callback
  *     callback.
+ * @return {undefined}
  */
 ChromeInputImeOnKeyEventEvent.prototype.removeListener = function(callback) {};
 
@@ -4254,6 +4522,7 @@
 /**
  * @param {function(string, !ChromeKeyboardEvent): (boolean|undefined)} callback
  *     callback.
+ * @return {undefined}
  */
 ChromeInputImeOnKeyEventEvent.prototype.hasListener = function(callback) {};
 
@@ -4261,6 +4530,7 @@
 /**
  * @param {function(string, !ChromeKeyboardEvent): (boolean|undefined)} callback
  *     callback.
+ * @return {undefined}
  */
 ChromeInputImeOnKeyEventEvent.prototype.hasListeners = function(callback) {};
 
@@ -4269,6 +4539,7 @@
  * @param {!Object<string,number>} parameters An object with a
  *     'contextID' (number) key.
  * @param {function(boolean): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.clearComposition = function(parameters, callback) {};
 
@@ -4277,6 +4548,7 @@
  * @param {!Object<string,(string|number)>} parameters An object with
  *     'contextID' (number) and 'text' (string) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.commitText = function(parameters, opt_callback) {};
 
@@ -4285,6 +4557,7 @@
  * @param {!Object<string,(string|number)>} parameters An object with
  *     'contextID' (number) and 'text' (string) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.deleteSurroundingText = function(parameters, opt_callback) {};
 
@@ -4294,6 +4567,7 @@
  *     parameters An object with 'engineID' (string) and 'properties'
  *     (Object) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.setCandidateWindowProperties =
     function(parameters, opt_callback) {};
@@ -4304,6 +4578,7 @@
  *     parameters An object with 'contextID' (number) and 'candidates'
  *     (array of object) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.setCandidates = function(parameters, opt_callback) {};
 
@@ -4314,6 +4589,7 @@
  *     'selectionStart (number), 'selectionEnd' (number), 'cursor' (number),
  *     and 'segments' (array of object) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.setComposition = function(parameters, opt_callback) {};
 
@@ -4322,6 +4598,7 @@
  * @param {!Object<string,number>} parameters An object with
  *     'contextID' (number) and 'candidateID' (number) keys.
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.setCursorPosition = function(parameters, opt_callback) {};
 
@@ -4331,6 +4608,7 @@
  *     parameters An object with 'engineID' (string) and 'items'
  *     (array of object) keys.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.setMenuItems = function(parameters, opt_callback) {};
 
@@ -4340,6 +4618,7 @@
  *     parameters An object with  'engineID' (string) and 'items'
  *     (array of object) keys.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.input.ime.updateMenuItems = function(parameters, opt_callback) {};
 
@@ -4348,6 +4627,7 @@
  * @param {string} requestId Request id of the event that was handled. This
  *     should come from keyEvent.requestId.
  * @param {boolean} response True if the keystroke was handled, false if not.
+ * @return {undefined}
  */
 chrome.input.ime.keyEventHandled = function(requestId, response) {};
 
@@ -4406,6 +4686,7 @@
  *     interactive if permissions haven't been granted yet or the callback.
  * @param {function(!Array<!FileSystem>)=} opt_callback A success callback if
  *     no details were supplied as arg1.
+ * @return {undefined}
  */
 chrome.mediaGalleries.getMediaFileSystems = function(
     detailsOrCallback, opt_callback) {};
@@ -4413,6 +4694,7 @@
 
 /**
  * @param {function(!Array<!FileSystem>, string)} callback Callback function.
+ * @return {undefined}
  */
 chrome.mediaGalleries.addUserSelectedFolder = function(callback) {};
 
@@ -4420,6 +4702,7 @@
 /**
  * @param {string} galleryId ID of the media gallery.
  * @param {function()=} opt_callback Optional callback function.
+ * @return {undefined}
  */
 chrome.mediaGalleries.dropPermissionForMediaFileSystem =
     function(galleryId, opt_callback) {};
@@ -4433,6 +4716,7 @@
 
 /**
  * @param {function(!Array<!FileSystem>)} callback Callback function.
+ * @return {undefined}
  */
 chrome.mediaGalleries.addScanResults = function(callback) {};
 
@@ -4460,6 +4744,7 @@
 /**
  * @param {function(!Array<!chrome.mediaGalleries.MediaFileSystemMetadata>)}
  *     callback Callback function.
+ * @return {undefined}
  */
 chrome.mediaGalleries.getAllMediaFileSystemMetadata = function(callback) {};
 
@@ -4521,6 +4806,7 @@
  *     'all' if the metadataType is omitted.
  * @param {function(!chrome.mediaGalleries.MetaData)=} opt_callback If options
  *     were passed as arg2, the callback to invoke with the metadata.
+ * @return {undefined}
  */
 chrome.mediaGalleries.getMetadata = function(
     mediaFile, optionsOrCallback, opt_callback) {};
@@ -4536,12 +4822,14 @@
  * @param {string} galleryId The media gallery's ID.
  * @param {!chrome.mediaGalleries.AddGalleryWatchCallback} callback Fired with
  *     success or failure result.
+ * @return {undefined}
  */
 chrome.mediaGalleries.addGalleryWatch = function(galleryId, callback) {};
 
 
 /**
  * @param {string} galleryId The media gallery's ID.
+ * @return {undefined}
  */
 chrome.mediaGalleries.removeGalleryWatch = function(galleryId) {};
 
@@ -4549,6 +4837,7 @@
 /**
  * @param {function(!Array<string>): void} callback Callback function notifies
  *     which galleries are being watched.
+ * @return {undefined}
  */
 chrome.mediaGalleries.getAllGalleryWatch = function(callback) {};
 
@@ -4571,6 +4860,7 @@
 
 /**
  * @param {!chrome.mediaGalleries.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleries.GalleryChangeEvent.prototype.addListener =
     function(callback) {};
@@ -4578,6 +4868,7 @@
 
 /**
  * @param {!chrome.mediaGalleries.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleries.GalleryChangeEvent.prototype.removeListener =
     function(callback) {};
@@ -4585,6 +4876,7 @@
 
 /**
  * @param {!chrome.mediaGalleries.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleries.GalleryChangeEvent.prototype.hasListener =
     function(callback) {};
@@ -4623,12 +4915,18 @@
 chrome.mediaGalleries.ScanProgressEvent = function() {};
 
 
-/** @param {function(!chrome.mediaGalleries.OnScanProgressDetails)} callback */
+/**
+ * @param {function(!chrome.mediaGalleries.OnScanProgressDetails)} callback
+ * @return {undefined}
+ */
 chrome.mediaGalleries.ScanProgressEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.mediaGalleries.OnScanProgressDetails)} callback */
+/**
+ * @param {function(!chrome.mediaGalleries.OnScanProgressDetails)} callback
+ * @return {undefined}
+ */
 chrome.mediaGalleries.ScanProgressEvent.prototype.removeListener =
     function(callback) {};
 
@@ -4659,6 +4957,7 @@
 /**
  * @param {Object<string, number>} details Object with a 'tabId' (number) key.
  * @param {function(Blob=): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.pageCapture.saveAsMHTML = function(details, callback) {};
 
@@ -4683,6 +4982,7 @@
 /**
  * @param {!chrome.permissions.Permissions} permissions
  * @param {function(boolean): void} callback Callback function.
+ * @return {undefined}
  */
 chrome.permissions.contains = function(permissions, callback) {};
 
@@ -4690,6 +4990,7 @@
 /**
  * @param {function(!chrome.permissions.Permissions): void} callback
  *     Callback function.
+ * @return {undefined}
  */
 chrome.permissions.getAll = function(callback) {};
 
@@ -4697,6 +4998,7 @@
 /**
  * @param {!chrome.permissions.Permissions} permissions
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.permissions.remove = function(permissions, opt_callback) {};
 
@@ -4704,6 +5006,7 @@
 /**
  * @param {!chrome.permissions.Permissions} permissions
  * @param {function(boolean): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.permissions.request = function(permissions, opt_callback) {};
 
@@ -4725,12 +5028,14 @@
 /**
  * @param {string} level A string describing the degree to which power
  *     management should be disabled, should be either "system" or "display".
+ * @return {undefined}
  */
 chrome.power.requestKeepAwake = function(level) {};
 
 
 /**
  * Releases a request previously made via requestKeepAwake().
+ * @return {undefined}
  */
 chrome.power.releaseKeepAwake = function() {};
 
@@ -4903,12 +5208,14 @@
  *     socket options or callback.
  * @param {function(!chrome.socket.CreateInfo)=} opt_callback Called when the
  *     socket has been created.
+ * @return {undefined}
  */
 chrome.socket.create = function(type, optionsOrCallback, opt_callback) {};
 
 
 /**
  * @param {number} socketId The id of the socket to destroy.
+ * @return {undefined}
  */
 chrome.socket.destroy = function(socketId) {};
 
@@ -4919,6 +5226,7 @@
  * @param {number} port The port of the remote machine.
  * @param {function(number)} callback Called when the connection attempt is
  *     complete.
+ * @return {undefined}
  */
 chrome.socket.connect = function(socketId, hostname, port, callback) {};
 
@@ -4928,12 +5236,14 @@
  * @param {string} address The address of the local machine.
  * @param {number} port The port of the local machine.
  * @param {function(number)} callback Called when the bind attempt is complete.
+ * @return {undefined}
  */
 chrome.socket.bind = function(socketId, address, port, callback) {};
 
 
 /**
  * @param {number} socketId The id of the socket to disconnect.
+ * @return {undefined}
  */
 chrome.socket.disconnect = function(socketId) {};
 
@@ -4944,6 +5254,7 @@
  *     read buffer size or the callback.
  * @param {function(!chrome.socket.ReadInfo)=} opt_callback Called with data
  *     that was available to be read without blocking.
+ * @return {undefined}
  */
 chrome.socket.read = function(socketId, bufferSizeOrCallback, opt_callback) {};
 
@@ -4953,6 +5264,7 @@
  * @param {!ArrayBuffer} data The data to write.
  * @param {function(!chrome.socket.WriteInfo)} callback Called when the write
  *     operation completes without blocking or an error occurs.
+ * @return {undefined}
  */
 chrome.socket.write = function(socketId, data, callback) {};
 
@@ -4963,6 +5275,7 @@
  *     The read buffer size or the callback.
  * @param {function(!chrome.socket.RecvFromInfo)=} opt_callback Called with data
  *     that was available to be read without blocking.
+ * @return {undefined}
  */
 chrome.socket.recvFrom = function(socketId, bufferSizeOrCallback,
     opt_callback) {};
@@ -4975,6 +5288,7 @@
  * @param {number} port The port of the remote machine.
  * @param {function(!chrome.socket.WriteInfo)} callback Called when the write
  *     operation completes without blocking or an error occurs.
+ * @return {undefined}
  */
 chrome.socket.sendTo = function(socketId, data, address, port, callback) {};
 
@@ -4988,6 +5302,7 @@
  *     socket's listen queue or the callback.
  * @param {function(number)=} opt_callback Called when the listen operation
  *     completes.
+ * @return {undefined}
  */
 chrome.socket.listen =
     function(socketId, address, port, backlogOrCallback, opt_callback) {};
@@ -4997,6 +5312,7 @@
  * @param {number} socketId The id of the socket to accept a connection on.
  * @param {function(!chrome.socket.AcceptInfo)} callback Called when a new
  *     socket is accepted.
+ * @return {undefined}
  */
 chrome.socket.accept = function(socketId, callback) {};
 
@@ -5009,6 +5325,7 @@
  *     is 0) or the callback
  * @param {function(boolean)=} opt_callback Called when the setKeepAlive attempt
  *     is complete.
+ * @return {undefined}
  */
 chrome.socket.setKeepAlive = function(socketId, enable, delayOrCallback,
     opt_callback) {};
@@ -5019,6 +5336,7 @@
  * @param {boolean} noDelay If true, disables Nagle's algorithm.
  * @param {function(boolean)} callback Called when the setNoDelay attempt is
  *     complete.
+ * @return {undefined}
  */
 chrome.socket.setNoDelay = function(socketId, noDelay, callback) {};
 
@@ -5027,6 +5345,7 @@
  * @param {number} socketId The id of the socket.
  * @param {function(!chrome.socket.SocketInfo)} callback Called when the state
  *     is available.
+ * @return {undefined}
  */
 chrome.socket.getInfo = function(socketId, callback) {};
 
@@ -5034,6 +5353,7 @@
 /**
  * @param {function(!Array<!chrome.socket.NetworkAdapterInfo>)} callback Called
  *     when local adapter information is available.
+ * @return {undefined}
  */
 chrome.socket.getNetworkList = function(callback) {};
 
@@ -5043,6 +5363,7 @@
  * @param {string} address The group address to join. Domain names are not
  *     supported.
  * @param {function(number)} callback Called when the join operation is done.
+ * @return {undefined}
  */
 chrome.socket.joinGroup = function(socketId, address, callback) {};
 
@@ -5052,6 +5373,7 @@
  * @param {string} address The group address to leave. Domain names are not
  *     supported.
  * @param {function(number)} callback Called when the leave operation is done.
+ * @return {undefined}
  */
 chrome.socket.leaveGroup = function(socketId, address, callback) {};
 
@@ -5061,6 +5383,7 @@
  * @param {number} ttl The time-to-live value.
  * @param {function(number)} callback Called when the configuration operation is
  *     done.
+ * @return {undefined}
  */
 chrome.socket.setMulticastTimeToLive = function(socketId, ttl, callback) {};
 
@@ -5070,6 +5393,7 @@
  * @param {boolean} enabled True to enable loopback mode.
  * @param {function(number)} callback Called when the configuration operation is
  *     done.
+ * @return {undefined}
  */
 chrome.socket.setMulticastLoopbackMode = function(socketId, enabled,
     callback) {};
@@ -5079,6 +5403,7 @@
  * @param {number} socketId The id of the socket.
  * @param {function(!Array<string>)} callback Called with an array of string
  *     groups.
+ * @return {undefined}
  */
 chrome.socket.getJoinedGroups = function(socketId, callback) {};
 
@@ -5179,6 +5504,7 @@
  *     function(!Object)} propertiesOrCallback
  * @param {function(!Object)=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-create
+ * @return {undefined}
  */
 chrome.sockets.tcp.create = function(propertiesOrCallback, opt_callback) {};
 
@@ -5188,6 +5514,7 @@
  * @param {!chrome.sockets.tcp.SocketProperties} properties
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-update
+ * @return {undefined}
  */
 chrome.sockets.tcp.update = function(socketId, properties, opt_callback) {};
 
@@ -5197,6 +5524,7 @@
  * @param {boolean} paused
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-setPaused
+ * @return {undefined}
  */
 chrome.sockets.tcp.setPaused = function(socketId, paused, opt_callback) {};
 
@@ -5207,6 +5535,7 @@
  * @param {(number|function(number))} delayOrCallback
  * @param {function(number)=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-setKeepAlive
+ * @return {undefined}
  */
 chrome.sockets.tcp.setKeepAlive = function(socketId, enable, delayOrCallback,
     opt_callback) {};
@@ -5217,6 +5546,7 @@
  * @param {boolean} noDelay
  * @param {function(number)} callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-setNoDelay
+ * @return {undefined}
  */
 chrome.sockets.tcp.setNoDelay = function(socketId, noDelay, callback) {};
 
@@ -5227,6 +5557,7 @@
  * @param {number} peerPort
  * @param {function(number)} callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-connect
+ * @return {undefined}
  */
 chrome.sockets.tcp.connect = function(socketId, peerAddress, peerPort,
     callback) {};
@@ -5236,6 +5567,7 @@
  * @param {number} socketId The id of the socket to disconnect.
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-disconnect
+ * @return {undefined}
  */
 chrome.sockets.tcp.disconnect = function(socketId, opt_callback) {};
 
@@ -5246,6 +5578,7 @@
  *     optionsOrCallback
  * @param {function(number)=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-secure
+ * @return {undefined}
  */
 chrome.sockets.tcp.secure = function(socketId, optionsOrCallback,
     opt_callback) {};
@@ -5256,6 +5589,7 @@
  * @param {!ArrayBuffer} data
  * @param {function(!Object)} callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-send
+ * @return {undefined}
  */
 chrome.sockets.tcp.send = function(socketId, data, callback) {};
 
@@ -5264,6 +5598,7 @@
  * @param {number} socketId
  * @param {function()=} opt_callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-close
+ * @return {undefined}
  */
 chrome.sockets.tcp.close = function(socketId, opt_callback) {};
 
@@ -5272,6 +5607,7 @@
  * @param {number} socketId
  * @param {function(!chrome.sockets.tcp.SocketInfo)} callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-getInfo
+ * @return {undefined}
  */
 chrome.sockets.tcp.getInfo = function(socketId, callback) {};
 
@@ -5279,6 +5615,7 @@
 /**
  * @param {function(!Array<!chrome.sockets.tcp.SocketInfo>)} callback
  * @see https://developer.chrome.com/apps/sockets_tcp#method-getSockets
+ * @return {undefined}
  */
 chrome.sockets.tcp.getSockets = function(callback) {};
 
@@ -5309,6 +5646,7 @@
 
 /**
  * @param {function(!chrome.sockets.tcp.ReceiveEventData): void} callback
+ * @return {undefined}
  */
 chrome.sockets.tcp.ReceiveEvent.prototype.addListener =
     function(callback) {};
@@ -5316,6 +5654,7 @@
 
 /**
  * @param {function(!chrome.sockets.tcp.ReceiveEventData): void} callback
+ * @return {undefined}
  */
 chrome.sockets.tcp.ReceiveEvent.prototype.removeListener =
     function(callback) {};
@@ -5364,6 +5703,7 @@
 /**
  * @param {function(
  *     !chrome.sockets.tcp.ReceiveErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.sockets.tcp.ReceiveErrorEvent.prototype.addListener =
     function(callback) {};
@@ -5372,6 +5712,7 @@
 /**
  * @param {function(
  *     !chrome.sockets.tcp.ReceiveErrorEventData): void} callback
+ * @return {undefined}
  */
 chrome.sockets.tcp.ReceiveErrorEvent.prototype.removeListener =
     function(callback) {};
@@ -5431,6 +5772,7 @@
 
 /**
  * @param {function(!Object)} callback
+ * @return {undefined}
  */
 chrome.system.cpu.getInfo = function(callback) {};
 
@@ -5563,6 +5905,7 @@
 /**
  * @param {function(!Array<!chrome.system.display.DisplayInfo>)}
  *     callback Called with an array of objects representing display info.
+ * @return {undefined}
  */
 chrome.system.display.getInfo = function(callback) {};
 
@@ -5573,6 +5916,7 @@
  *     about display properties that should be changed.
  * @param {function()=} opt_callback The callback to execute when the display
  *     info has been changed.
+ * @return {undefined}
  */
 chrome.system.display.setDisplayProperties =
     function(id, info, opt_callback) {};
@@ -5600,6 +5944,7 @@
  * @param {Object} details Object with a 'tabId' (number) key.
  * @param {function(!Array<Object<string, (boolean|number|string)>>)} callback
  *     Callback function.
+ * @return {undefined}
  */
 chrome.webNavigation.getAllFrames = function(details, callback) {};
 
@@ -5609,6 +5954,7 @@
  *     keys.
  * @param {function(Object<string, (boolean|string)>)} callback
  *     Callback function.
+ * @return {undefined}
  */
 chrome.webNavigation.getFrame = function(details, callback) {};
 
@@ -5665,6 +6011,7 @@
  *     the events that will be sent to this listener.
  * @param {Array<string>=} opt_extraInfoSpec Array of extra information
  *     that should be passed to the listener function.
+ * @return {undefined}
  */
 WebRequestEvent.prototype.addListener =
     function(listener, filter, opt_extraInfoSpec) {};
@@ -5673,6 +6020,7 @@
 /**
  * @param {function(!Object): (void|!BlockingResponse)} listener Listener
  *     function.
+ * @return {undefined}
  */
 WebRequestEvent.prototype.removeListener = function(listener) {};
 
@@ -5680,6 +6028,7 @@
 /**
  * @param {function(!Object): (void|!BlockingResponse)} listener Listener
  *     function.
+ * @return {undefined}
  */
 WebRequestEvent.prototype.hasListener = function(listener) {};
 
@@ -5687,6 +6036,7 @@
 /**
  * @param {function(!Object): (void|!BlockingResponse)} listener Listener
  *     function.
+ * @return {undefined}
  */
 WebRequestEvent.prototype.hasListeners = function(listener) {};
 
@@ -5704,6 +6054,7 @@
  * @param {function(!Object): void} listener Listener function.
  * @param {!RequestFilter} filter A set of filters that restrict
  *     the events that will be sent to this listener.
+ * @return {undefined}
  */
 WebRequestOnErrorOccurredEvent.prototype.addListener =
     function(listener, filter) {};
@@ -5711,18 +6062,21 @@
 
 /**
  * @param {function(!Object): void} listener Listener function.
+ * @return {undefined}
  */
 WebRequestOnErrorOccurredEvent.prototype.removeListener = function(listener) {};
 
 
 /**
  * @param {function(!Object): void} listener Listener function.
+ * @return {undefined}
  */
 WebRequestOnErrorOccurredEvent.prototype.hasListener = function(listener) {};
 
 
 /**
  * @param {function(!Object): void} listener Listener function.
+ * @return {undefined}
  */
 WebRequestOnErrorOccurredEvent.prototype.hasListeners = function(listener) {};
 
@@ -5736,6 +6090,7 @@
 
 /**
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 chrome.webRequest.handlerBehaviorChanged = function(opt_callback) {};
 
@@ -5949,11 +6304,17 @@
 function ChromeExtensionInfoEvent() {}
 
 
-/** @param {function(!ExtensionInfo): void} callback */
+/**
+ * @param {function(!ExtensionInfo): void} callback
+ * @return {undefined}
+ */
 ChromeExtensionInfoEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(!ExtensionInfo): void} callback */
+/**
+ * @param {function(!ExtensionInfo): void} callback
+ * @return {undefined}
+ */
 ChromeExtensionInfoEvent.prototype.removeListener = function(callback) {};
 
 
@@ -5989,6 +6350,7 @@
  *     not given, the callback.
  * @param {function(!chrome.pushMessaging.ChannelIdResult)=} opt_callback
  *     Callback.
+ * @return {undefined}
  */
 chrome.pushMessaging.getChannelId =
     function(interactiveOrCallback, opt_callback) {};
@@ -6004,6 +6366,7 @@
 
 /**
  * @param {function(!chrome.pushMessaging.Message): void} callback
+ * @return {undefined}
  */
 chrome.pushMessaging.PushMessageEvent.prototype.addListener =
     function(callback) {};
@@ -6011,6 +6374,7 @@
 
 /**
  * @param {function(!chrome.pushMessaging.Message): void} callback
+ * @return {undefined}
  */
 chrome.pushMessaging.PushMessageEvent.prototype.removeListener =
     function(callback) {};
@@ -6222,6 +6586,7 @@
 /**
  * @param {!Object<string,string>} details Settings details.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 ContentSetting.prototype.clear = function(details, opt_callback) {};
 
@@ -6230,12 +6595,14 @@
  * @param {!Object<string,(string|boolean|ResourceIdentifier)>} details
  *     Settings details.
  * @param {function(): void} callback Callback function.
+ * @return {undefined}
  */
 ContentSetting.prototype.get = function(details, callback) {};
 
 
 /**
  * @param {function(): void} callback Callback function.
+ * @return {undefined}
  */
 ContentSetting.prototype.getResourceIdentifiers = function(callback) {};
 
@@ -6244,6 +6611,7 @@
  * @param {!Object<string,(string|ResourceIdentifier)>} details
  *     Settings details.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 ContentSetting.prototype.set = function(details, opt_callback) {};
 
@@ -6496,19 +6864,29 @@
  *    Listener will receive an object that maps each key to its StorageChange,
  *    and the namespace ("sync" or "local") of the storage area the changes
  *    are for.
+ * @return {undefined}
  */
 StorageChangeEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(!Object<string, !StorageChange>, string)} callback */
+/**
+ * @param {function(!Object<string, !StorageChange>, string)} callback
+ * @return {undefined}
+ */
 StorageChangeEvent.prototype.removeListener = function(callback) {};
 
 
-/** @param {function(!Object<string, !StorageChange>, string)} callback */
+/**
+ * @param {function(!Object<string, !StorageChange>, string)} callback
+ * @return {undefined}
+ */
 StorageChangeEvent.prototype.hasListener = function(callback) {};
 
 
-/** @param {function(!Object<string, !StorageChange>, string)} callback */
+/**
+ * @param {function(!Object<string, !StorageChange>, string)} callback
+ * @return {undefined}
+ */
 StorageChangeEvent.prototype.hasListeners = function(callback) {};
 
 
@@ -6545,6 +6923,7 @@
  *    result object. Pass in null to get the entire contents of storage.
  * @param {function(!Object)=} opt_callback Callback with storage items, or null
  *    on failure.
+ * @return {undefined}
  */
 StorageArea.prototype.get = function(keysOrCallback, opt_callback) {};
 
@@ -6555,6 +6934,7 @@
  *    will return 0. Pass in null to get the total usage of all of storage.
  * @param {function(number)=} opt_callback
  *    Callback with the amount of space being used by storage.
+ * @return {undefined}
  */
 StorageArea.prototype.getBytesInUse = function(keysOrCallback, opt_callback) {};
 
@@ -6564,6 +6944,7 @@
  *    Object specifying items to augment storage
  *    with. Values that cannot be serialized (functions, etc) will be ignored.
  * @param {function()=} opt_callback Callback.
+ * @return {undefined}
  */
 StorageArea.prototype.set = function(items, opt_callback) { };
 
@@ -6572,6 +6953,7 @@
  * @param {(string|!Array<string>)} keys
  *    A single key or a list of keys for items to remove.
  * @param {function()=} opt_callback Callback.
+ * @return {undefined}
  */
 StorageArea.prototype.remove = function(keys, opt_callback) {};
 
@@ -6579,6 +6961,7 @@
 /**
  * Removes all items from storage.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 StorageArea.prototype.clear = function(opt_callback) {};
 
@@ -6594,6 +6977,7 @@
 /**
  * @param {Object} details Object with a 'scope' (string) key.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 ChromeSetting.prototype.clear = function(details, opt_callback) {};
 
@@ -6601,6 +6985,7 @@
 /**
  * @param {Object} details Object with an 'incognito' (boolean) key.
  * @param {function(Object<string, *>): void} callback Callback function.
+ * @return {undefined}
  */
 ChromeSetting.prototype.get = function(details, callback) {};
 
@@ -6609,6 +6994,7 @@
  * @param {Object} details Object with a 'value' (*) key and an optional
  *     'scope' (string) key.
  * @param {function(): void=} opt_callback Callback function.
+ * @return {undefined}
  */
 ChromeSetting.prototype.set = function(details, opt_callback) {};
 
@@ -6747,6 +7133,7 @@
  *     {@code chrome.fileSystem.restoreEntry}.
  * @param {function(string)} callback A success callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-getDisplayPath
+ * @return {undefined}
  */
 chrome.fileSystem.getDisplayPath = function(entry, callback) {};
 
@@ -6755,6 +7142,7 @@
  * @param {!Entry} entry The entry to get a writable entry for.
  * @param {function(!Entry)} callback A success callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-getWritableEntry
+ * @return {undefined}
  */
 chrome.fileSystem.getWritableEntry = function(entry, callback) {};
 
@@ -6763,6 +7151,7 @@
  * @param {!Entry} entry The entry to query writability.
  * @param {function(boolean)} callback A success callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-isWritableEntry
+ * @return {undefined}
  */
 chrome.fileSystem.isWritableEntry = function(entry, callback) {};
 
@@ -6823,6 +7212,7 @@
  * @param {function(Entry=, !Array<!FileEntry>=)=} opt_callback A success
  *     callback, if arg1 is options.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-chooseEntry
+ * @return {undefined}
  */
 chrome.fileSystem.chooseEntry = function(optionsOrCallback, opt_callback) {};
 
@@ -6831,6 +7221,7 @@
  * @param {string} id The ID of the file entry to restore.
  * @param {function(!Entry)} callback A success callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-restoreEntry
+ * @return {undefined}
  */
 chrome.fileSystem.restoreEntry = function(id, callback) {};
 
@@ -6839,6 +7230,7 @@
  * @param {string} id The ID of the file entry to query restorability.
  * @param {function(boolean)} callback A success callback.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-isRestorable
+ * @return {undefined}
  */
 chrome.fileSystem.isRestorable = function(id, callback) {};
 
@@ -6859,6 +7251,7 @@
  *     system in case of a success. Otherwise the error is passed as
  *     chrome.runtime.lastError.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-requestFileSystem
+ * @return {undefined}
  */
 chrome.fileSystem.requestFileSystem = function(options, callback) {};
 
@@ -6868,6 +7261,7 @@
  *     callback with the file system list in case of a success. Otherwise the
  *     error is passed as chrome.runtime.lastError.
  * @see http://developer.chrome.com/apps/fileSystem.html#method-getVolumeList
+ * @return {undefined}
  */
 chrome.fileSystem.getVolumeList = function(callback) {};
 
@@ -6899,6 +7293,7 @@
  * @param {function(!FileSystem)} callback A callback type for
  *     requestFileSystem.
  * @see https://developer.chrome.com/apps/syncFileSystem#method-requestFileSystem
+ * @return {undefined}
  */
 chrome.syncFileSystem.requestFileSystem = function(callback) {};
 
@@ -6915,6 +7310,7 @@
  * @param {function()=} opt_callback
  *
  * @see https://developer.chrome.com/apps/syncFileSystem#method-setConflictResolutionPolicy
+ * @return {undefined}
  */
 chrome.syncFileSystem.setConflictResolutionPolicy =
     function(policy, opt_callback) {};
@@ -6926,6 +7322,7 @@
  * @param {function(string)} callback Accepting any of 'last_write_win'
  *     or 'manual'.
  * @see https://developer.chrome.com/apps/syncFileSystem#method-getConflictResolutionPolicy
+ * @return {undefined}
  */
 chrome.syncFileSystem.getConflictResolutionPolicy = function(callback) {};
 
@@ -6938,6 +7335,7 @@
  * @param {function(!Object)} callback Taking an object substantially similar
  *     to {@code {'usageBytes': number, quotaBytes: number}}.
  * @see https://developer.chrome.com/apps/syncFileSystem#method-getUsageAndQuota
+ * @return {undefined}
  */
 chrome.syncFileSystem.getUsageAndQuota = function(fileSystem, callback) {};
 
@@ -6952,6 +7350,7 @@
  *     or 'conflicting'.
  *
  * @see https://developer.chrome.com/apps/syncFileSystem#method-getFileStatus
+ * @return {undefined}
  */
 chrome.syncFileSystem.getFileStatus = function(fileEntry, callback) {};
 
@@ -6965,6 +7364,7 @@
  *     {@code {'fileEntry': Entry, 'status': string, 'error': string?}}.
  *
  * @see https://developer.chrome.com/apps/syncFileSystem#method-getFileStatuses
+ * @return {undefined}
  */
 chrome.syncFileSystem.getFileStatuses = function(fileEntries, callback) {};
 
@@ -6978,6 +7378,7 @@
  *     'authentication_required', 'temporary_unavailable', or 'disabled'.
  *
  * @see https://developer.chrome.com/apps/syncFileSystem#method-getServiceStatus
+ * @return {undefined}
  */
 chrome.syncFileSystem.getServiceStatus = function(callback) {};
 
@@ -7021,6 +7422,7 @@
  * @param {!chrome.alarms.AlarmCreateInfo=} opt_alarmInfo If a name was passed
  *     as arg1, the info used to create the alarm.
  * @see http://developer.chrome.com/extensions/alarms.html#method-create
+ * @return {undefined}
  */
 chrome.alarms.create = function(nameOrAlarmCreateInfo, opt_alarmInfo) {};
 
@@ -7033,6 +7435,7 @@
  * @param {function(!chrome.alarms.Alarm)=} opt_callback If a name was passed
  *     as arg1, the callback to invoke with the alarm.
  * @see http://developer.chrome.com/extensions/alarms.html#method-get
+ * @return {undefined}
  */
 chrome.alarms.get = function(nameOrCallback, opt_callback) {};
 
@@ -7041,6 +7444,7 @@
  * Gets an array of all the alarms.
  * @param {function(!Array<!chrome.alarms.Alarm>)} callback
  * @see http://developer.chrome.com/extensions/alarms.html#method-getAll
+ * @return {undefined}
  */
 chrome.alarms.getAll = function(callback) {};
 
@@ -7051,6 +7455,7 @@
  * @param {function(boolean)=} opt_callback A callback that will be called with
  *     a boolean for whether the alarm was cleared.
  * @see http://developer.chrome.com/extensions/alarms.html#method-clear
+ * @return {undefined}
  */
 chrome.alarms.clear = function(opt_name, opt_callback) {};
 
@@ -7060,6 +7465,7 @@
  * @param {function(boolean)=} opt_callback A callback that will be called with
  *     a boolean for whether the alarms were cleared.
  * @see http://developer.chrome.com/extensions/alarms.html#method-clearAll
+ * @return {undefined}
  */
 chrome.alarms.clearAll = function(opt_callback) {};
 
@@ -7081,12 +7487,14 @@
 
 /**
  * @param {function(!chrome.alarms.Alarm): void} callback
+ * @return {undefined}
  */
 chrome.alarms.AlarmEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {function(!chrome.alarms.Alarm): void} callback
+ * @return {undefined}
  */
 chrome.alarms.AlarmEvent.prototype.removeListener = function(callback) {};
 
@@ -7322,6 +7730,7 @@
  *     for on target devices.
  * @param {function(!Array<!chrome.hid.HidDeviceInfo>)} callback Invoked with a
  *     list of |HidDeviceInfo|s on complete.
+ * @return {undefined}
  */
 chrome.hid.getDevices = function(options, callback) {};
 
@@ -7336,6 +7745,7 @@
  * @param {!chrome.hid.HidGetUserSelectedDevicesOptions} options
  * @param {function(!Array<!chrome.hid.HidDeviceInfo>)} callback Invoked with a
  *     list of |HidDeviceInfo|s on complete.
+ * @return {undefined}
  */
 chrome.hid.getUserSelectedDevices = function(options, callback) {};
 
@@ -7346,6 +7756,7 @@
  * @param {number} deviceId The ID of the device to open.
  * @param {function(!Object=)} callback Invoked with an |HidConnectInfo| if the
  *     connection succeeds, or undefined if it fails.
+ * @return {undefined}
  */
 chrome.hid.connect = function(deviceId, callback) {};
 
@@ -7356,6 +7767,7 @@
  * @param {number} connectionId The connection to close.
  * @param {function()=} opt_callback The callback to invoke once the connection
  *     is closed.
+ * @return {undefined}
  */
 chrome.hid.disconnect = function(connectionId, opt_callback) {};
 
@@ -7366,6 +7778,7 @@
  * @param {number} connectionId The connection from which to receive the report.
  * @param {function(number, !ArrayBuffer)} callback The callback to invoke with
  *     the received report.
+ * @return {undefined}
  */
 chrome.hid.receive = function(connectionId, callback) {};
 
@@ -7378,6 +7791,7 @@
  * @param {!ArrayBuffer} data The report data.
  * @param {function()} callback The callback to invoke once the write is
  *     finished.
+ * @return {undefined}
  */
 chrome.hid.send = function(connectionId, reportId, data, callback) {};
 
@@ -7391,6 +7805,7 @@
  * @param {number} size The size of the feature report to receive.
  * @param {function(!ArrayBuffer)} callback The callback to invoke with the
  *     received report.
+ * @return {undefined}
  */
 chrome.hid.receiveFeatureReport =
     function(connectionId, reportId, size, callback) {};
@@ -7405,6 +7820,7 @@
  * @param {!ArrayBuffer} data The report data.
  * @param {function()} callback The callback to invoke once the write is
  *     finished.
+ * @return {undefined}
  */
 chrome.hid.sendFeatureReport =
     function(connectionId, reportId, data, callback) {};
@@ -7418,12 +7834,18 @@
 chrome.hid.DeviceAddedEvent = function() {};
 
 
-/** @param {function(!chrome.hid.HidDeviceInfo): void} callback */
+/**
+ * @param {function(!chrome.hid.HidDeviceInfo): void} callback
+ * @return {undefined}
+ */
 chrome.hid.DeviceAddedEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.hid.HidDeviceInfo): void} callback */
+/**
+ * @param {function(!chrome.hid.HidDeviceInfo): void} callback
+ * @return {undefined}
+ */
 chrome.hid.DeviceAddedEvent.prototype.removeListener =
     function(callback) {};
 
@@ -7537,6 +7959,7 @@
  *     opt_optionsOrCallback
  * @param {(function(string): void)=} opt_callback
  * @see http://developer.chrome.com/extensions/notifications.html#method-create
+ * @return {undefined}
  */
 chrome.notifications.create = function(notificationIdOrOptions,
     opt_optionsOrCallback, opt_callback) {};
@@ -7547,6 +7970,7 @@
  * @param {!chrome.notifications.NotificationOptions} options
  * @param {chrome.notifications.BooleanCallback=} opt_callback
  * @see http://developer.chrome.com/extensions/notifications.html#method-update
+ * @return {undefined}
  */
 chrome.notifications.update =
     function(notificationId, options, opt_callback) {};
@@ -7556,6 +7980,7 @@
  * @param {string} notificationId
  * @param {!chrome.notifications.BooleanCallback=} opt_callback
  * @see http://developer.chrome.com/extensions/notifications.html#method-clear
+ * @return {undefined}
  */
 chrome.notifications.clear = function(notificationId, opt_callback) {};
 
@@ -7563,6 +7988,7 @@
 /**
  * @see http://developer.chrome.com/extensions/notifications.html#method-getAll
  * @param {!chrome.notifications.ObjectCallback} callback
+ * @return {undefined}
  */
 chrome.notifications.getAll = function(callback) {};
 
@@ -7570,6 +7996,7 @@
 /**
  * @see http://developer.chrome.com/extensions/notifications.html#method-getPermissionLevel
  * @param {function(string): void} callback takes 'granted' or 'denied'
+ * @return {undefined}
  */
 chrome.notifications.getPermissionLevel = function(callback) {};
 
@@ -7623,12 +8050,14 @@
 
 /**
  * @param {!chrome.notifications.ClosedCallback} callback
+ * @return {undefined}
  */
 chrome.notifications.ClosedEvent.prototype.addListener = function(callback) {};
 
 
 /**
  * @param {!chrome.notifications.ClosedCallback} callback
+ * @return {undefined}
  */
 chrome.notifications.ClosedEvent.prototype.removeListener =
     function(callback) {};
@@ -7657,6 +8086,7 @@
 
 /**
  * @param {!chrome.notifications.ButtonCallback} callback
+ * @return {undefined}
  */
 chrome.notifications.ButtonClickedEvent.prototype.addListener =
     function(callback) {};
@@ -7664,6 +8094,7 @@
 
 /**
  * @param {!chrome.notifications.ButtonCallback} callback
+ * @return {undefined}
  */
 chrome.notifications.ButtonClickedEvent.prototype.removeListener =
     function(callback) {};
@@ -7719,12 +8150,18 @@
 chrome.system.storage.StorageUnitInfoEvent = function() {};
 
 
-/** @param {function(!chrome.system.storage.StorageUnitInfo): void} callback */
+/**
+ * @param {function(!chrome.system.storage.StorageUnitInfo): void} callback
+ * @return {undefined}
+ */
 chrome.system.storage.StorageUnitInfoEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.system.storage.StorageUnitInfo): void} callback */
+/**
+ * @param {function(!chrome.system.storage.StorageUnitInfo): void} callback
+ * @return {undefined}
+ */
 chrome.system.storage.StorageUnitInfoEvent.prototype.removeListener =
     function(callback) {};
 
@@ -7753,6 +8190,7 @@
 /**
  * Gets the storage information from the system.
  * @param {function(!Array<!chrome.system.storage.StorageUnitInfo>)} callback
+ * @return {undefined}
  */
 chrome.system.storage.getInfo = function(callback) {};
 
@@ -7762,6 +8200,7 @@
  * @param {string} id The transient device ID from StorageUnitInfo.
  * @param {function(string)} callback Callback function where the value
  *     is any of: "success", "in_use", "no_such_device", "failure"
+ * @return {undefined}
  */
 chrome.system.storage.ejectDevice = function(id, callback) {};
 
@@ -7771,6 +8210,7 @@
  * @param {string} id The transient device ID from StorageUnitInfo.
  * @param {function(Object<string, number>)} callback A callback function that
  *     accepts an object with {@code id} and {@code availableCapacity} fields.
+ * @return {undefined}
  */
 chrome.system.storage.getAvailableCapacity = function(id, callback) {};
 
@@ -7885,7 +8325,7 @@
 
 /**
  * @typedef {?{
- *   deviceId: number,
+ *   vendorId: number,
  *   productId: number,
  *   interfaceId: (number|undefined)
  * }}
@@ -8050,6 +8490,7 @@
  *     search for on target devices.
  * @param {function(!Array<!chrome.usb.Device>)} callback Invoked with a list
  *     of |Device|s on complete.
+ * @return {undefined}
  */
 chrome.usb.getDevices = function(options, callback) {};
 
@@ -8060,6 +8501,7 @@
  *     the device picker dialog box.
  * @param {function(!Array<!chrome.usb.Device>)} callback Invoked with a list
  *     of |Device|s on complete.
+ * @return {undefined}
  */
 chrome.usb.getUserSelectedDevices = function(options, callback) {};
 
@@ -8069,6 +8511,7 @@
  * @param {!chrome.usb.Device} device The device to fetch descriptors from.
  * @param {function(!Array<!chrome.usb.ConfigDescriptor>)} callback Invoked with
  *     the full set of device configuration descriptors.
+ * @return {undefined}
  */
 chrome.usb.getConfigurations = function(device, callback) {};
 
@@ -8078,6 +8521,7 @@
  * @param {!chrome.usb.Device} device The device to request access to.
  * @param {number} interfaceId
  * @param {function(boolean)} callback
+ * @return {undefined}
  */
 chrome.usb.requestAccess = function(device, interfaceId, callback) {};
 
@@ -8087,6 +8531,7 @@
  * @param {!chrome.usb.Device} device The device to open.
  * @param {function(!chrome.usb.ConnectionHandle)} callback Invoked with the
  *     created ConnectionHandle on complete.
+ * @return {undefined}
  */
 chrome.usb.openDevice = function(device, callback) {};
 
@@ -8097,6 +8542,7 @@
  *     on target devices.
  * @param {function(!Array<!chrome.usb.ConnectionHandle>)} callback Invoked
  *     with the opened ConnectionHandle on complete.
+ * @return {undefined}
  */
 chrome.usb.findDevices = function(options, callback) {};
 
@@ -8106,6 +8552,7 @@
  * @param {!chrome.usb.ConnectionHandle} handle The connection handle to close.
  * @param {function()=} opt_callback The callback to invoke once the device is
  *     closed.
+ * @return {undefined}
  */
 chrome.usb.closeDevice = function(handle, opt_callback) {};
 
@@ -8116,6 +8563,7 @@
  *     to select a device configuration.
  * @param {number} configurationValue The configuration to select.
  * @param {function()} callback The callback to invoke on complete.
+ * @return {undefined}
  */
 chrome.usb.setConfiguration = function(handle, configurationValue, callback) {};
 
@@ -8126,6 +8574,7 @@
  *     to get the current device configuration descriptor.
  * @param {function(!chrome.usb.ConfigDescriptor)} callback The callback to
  *     invoke on complete.
+ * @return {undefined}
  */
 chrome.usb.getConfiguration = function(handle, callback) {};
 
@@ -8136,6 +8585,7 @@
  *     interfaces should be listed.
  * @param {function(!Array<!chrome.usb.InterfaceDescriptor>)} callback The
  *     callback to invoke when the interfaces are enumerated.
+ * @return {undefined}
  */
 chrome.usb.listInterfaces = function(handle, callback) {};
 
@@ -8147,6 +8597,7 @@
  * @param {number} interfaceNumber
  * @param {function()} callback The callback to invoke once the interface is
  *     claimed.
+ * @return {undefined}
  */
 chrome.usb.claimInterface = function(handle, interfaceNumber, callback) {};
 
@@ -8158,6 +8609,7 @@
  * @param {number} interfaceNumber
  * @param {function()} callback The callback to invoke once the interface is
  *     released.
+ * @return {undefined}
  */
 chrome.usb.releaseInterface = function(handle, interfaceNumber, callback) {};
 
@@ -8170,6 +8622,7 @@
  * @param {number} alternateSetting The alternate setting to set.
  * @param {function()} callback The callback to invoke once the interface
  *     setting is set.
+ * @return {undefined}
  */
 chrome.usb.setInterfaceAlternateSetting = function(
     handle, interfaceNumber, alternateSetting, callback) {};
@@ -8183,6 +8636,7 @@
  *     transfer.
  * @param {function(!chrome.usb.TransferResultInfo)} callback Invoked once the
  *     transfer has completed.
+ * @return {undefined}
  */
 chrome.usb.controlTransfer = function(handle, transferInfo, callback) {};
 
@@ -8195,6 +8649,7 @@
  *     transfer. See GenericTransferInfo.
  * @param {function(!chrome.usb.TransferResultInfo)} callback Invoked once the
  *     transfer has completed.
+ * @return {undefined}
  */
 chrome.usb.bulkTransfer = function(handle, transferInfo, callback) {};
 
@@ -8207,6 +8662,7 @@
  *     transfer. See GenericTransferInfo.
  * @param {function(!chrome.usb.TransferResultInfo)} callback Invoked once the
  *     transfer has completed.
+ * @return {undefined}
  */
 chrome.usb.interruptTransfer = function(handle, transferInfo, callback) {};
 
@@ -8219,6 +8675,7 @@
  *     the transfer.
  * @param {function(!chrome.usb.TransferResultInfo)} callback Invoked once the
  *     transfer has been completed.
+ * @return {undefined}
  */
 chrome.usb.isochronousTransfer = function(handle, transferInfo, callback) {};
 
@@ -8228,6 +8685,7 @@
  * @param {!chrome.usb.ConnectionHandle} handle A connection handle to reset.
  * @param {function(boolean)} callback Invoked once the device is reset with a
  *     boolean indicating whether the reset completed successfully.
+ * @return {undefined}
  */
 chrome.usb.resetDevice = function(handle, callback) {};
 
@@ -8240,11 +8698,17 @@
 chrome.usb.DeviceEvent = function() {};
 
 
-/** @param {function(!chrome.usb.Device): void} callback */
+/**
+ * @param {function(!chrome.usb.Device): void} callback
+ * @return {undefined}
+ */
 chrome.usb.DeviceEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(!chrome.usb.Device): void} callback */
+/**
+ * @param {function(!chrome.usb.Device): void} callback
+ * @return {undefined}
+ */
 chrome.usb.DeviceEvent.prototype.removeListener = function(callback) {};
 
 
@@ -8325,6 +8789,7 @@
  * @param {function(!Array<!Object>)} callback Invoked with a
  *     list of ports on complete.
  * @see https://developer.chrome.com/apps/serial#method-getDevices
+ * @return {undefined}
  */
 chrome.serial.getDevices = function(callback) {};
 
@@ -8339,6 +8804,7 @@
  * @param {function(!chrome.serial.ConnectionInfo)=} opt_callback Invoked with
  *     the created ConnectionInfo on complete.
  * @see https://developer.chrome.com/apps/serial#method-connect
+ * @return {undefined}
  */
 chrome.serial.connect = function(path, optionsOrCallback, opt_callback) {};
 
@@ -8351,6 +8817,7 @@
  * @param {function(boolean)} callback Called when the configuration has
  *     completed.
  * @see https://developer.chrome.com/apps/serial#method-update
+ * @return {undefined}
  */
 chrome.serial.update = function(connectionId, options, callback) {};
 
@@ -8361,6 +8828,7 @@
  * @param {function(boolean)} callback Called when the connection
  *     has been closed.
  * @see https://developer.chrome.com/apps/serial#method-disconnect
+ * @return {undefined}
  */
 chrome.serial.disconnect = function(connectionId, callback) {};
 
@@ -8371,6 +8839,7 @@
  * @param {boolean} paused Flag to indicate whether to pause or unpause.
  * @param {function()} callback Called when the configuration has completed.
  * @see https://developer.chrome.com/apps/serial#method-setPaused
+ * @return {undefined}
  */
 chrome.serial.setPaused = function(connectionId, paused, callback) {};
 
@@ -8381,6 +8850,7 @@
  * @param {function(!chrome.serial.ConnectionInfo)} callback
  *     Called with connection state information when available.
  * @see https://developer.chrome.com/apps/serial#method-getInfo
+ * @return {undefined}
  */
 chrome.serial.getInfo = function(connectionId, callback) {};
 
@@ -8391,6 +8861,7 @@
  * @param {function(!Array<!chrome.serial.ConnectionInfo>)} callback
  *     Called with the list of |ConnectionInfo|s when available.
  * @see https://developer.chrome.com/apps/serial#method-getConnections
+ * @return {undefined}
  */
 chrome.serial.getConnections = function(callback) {};
 
@@ -8402,6 +8873,7 @@
  * @param {function(!Object)} callback Called when the operation has
  *     completed.
  * @see https://developer.chrome.com/apps/serial#method-send
+ * @return {undefined}
  */
 chrome.serial.send = function(connectionId, data, callback) {};
 
@@ -8411,6 +8883,7 @@
  * @param {number} connectionId The id of the opened connection.
  * @param {function(boolean)} callback
  * @see https://developer.chrome.com/apps/serial#method-flush
+ * @return {undefined}
  */
 chrome.serial.flush = function(connectionId, callback) {};
 
@@ -8422,6 +8895,7 @@
  * @param {number} connectionId The id of the opened connection.
  * @param {function(!Object)} callback
  * @see https://developer.chrome.com/apps/serial#method-getControlSignals
+ * @return {undefined}
  */
 chrome.serial.getControlSignals = function(connectionId, callback) {};
 
@@ -8443,6 +8917,7 @@
  * @param {function(boolean)} callback Called once the control signals
  *     have been set.
  * @see https://developer.chrome.com/apps/serial#method-setControlSignals
+ * @return {undefined}
  */
 chrome.serial.setControlSignals = function(connectionId, signals, callback) {};
 
@@ -8477,12 +8952,14 @@
 
 /**
  * @param {string} message Displayed on the unlock screen.
+ * @return {undefined}
  */
 chrome.screenlockPrivate.showMessage = function(message) {};
 
 
 /**
  * @param {function(boolean)} callback
+ * @return {undefined}
  */
 chrome.screenlockPrivate.getLocked = function(callback) {};
 
@@ -8490,6 +8967,7 @@
 /**
  * @param {boolean} locked If true and the screen is unlocked, locks the screen.
  *     If false and the screen is locked, unlocks the screen.
+ * @return {undefined}
  */
 chrome.screenlockPrivate.setLocked = function(locked) {};
 
@@ -8506,6 +8984,7 @@
 
 /**
  * @param {function(string): void} callback
+ * @return {undefined}
  */
 chrome.musicManagerPrivate.getDeviceId = function(callback) {};
 
@@ -8537,6 +9016,7 @@
 /**
  * @param {string} galleryId
  * @param {!chrome.mediaGalleriesPrivate.AddGalleryWatchCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.addGalleryWatch = function(galleryId, callback) {};
 
@@ -8572,6 +9052,7 @@
 /**
  * @param {!chrome.mediaGalleriesPrivate.DeviceCallback} callback
  * @deprecated Use {chrome.system.storage.DeviceEvent.addListener}.
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.DeviceEvent.prototype.addListener =
     function(callback) {};
@@ -8580,6 +9061,7 @@
 /**
  * @param {!chrome.mediaGalleriesPrivate.DeviceCallback} callback
  * @deprecated Use {chrome.system.storage.DeviceEvent.removeListener}.
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.DeviceEvent.prototype.removeListener =
     function(callback) {};
@@ -8588,6 +9070,7 @@
 /**
  * @param {!chrome.mediaGalleriesPrivate.DeviceCallback} callback
  * @deprecated Use {chrome.system.storage.DeviceEvent.hasListener}.
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.DeviceEvent.prototype.hasListener =
     function(callback) {};
@@ -8610,6 +9093,7 @@
 
 /**
  * @param {!chrome.mediaGalleriesPrivate.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.GalleryChangeEvent.prototype.addListener =
     function(callback) {};
@@ -8617,6 +9101,7 @@
 
 /**
  * @param {!chrome.mediaGalleriesPrivate.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.GalleryChangeEvent.prototype.removeListener =
     function(callback) {};
@@ -8624,6 +9109,7 @@
 
 /**
  * @param {!chrome.mediaGalleriesPrivate.GalleryChangeCallback} callback
+ * @return {undefined}
  */
 chrome.mediaGalleriesPrivate.GalleryChangeEvent.prototype.hasListener =
     function(callback) {};
@@ -8872,6 +9358,7 @@
 /**
  * @param {string} guid
  * @param {function(!Object)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getProperties = function(guid, callback) {};
 
@@ -8879,6 +9366,7 @@
 /**
  * @param {string} guid
  * @param {function(!Object)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getManagedProperties = function(guid, callback) {};
 
@@ -8886,6 +9374,7 @@
 /**
  * @param {string} guid
  * @param {function(!chrome.networkingPrivate.NetworkStateProperties)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getState = function(guid, callback) {};
 
@@ -8896,6 +9385,7 @@
  * @param {string} guid
  * @param {!Object} properties
  * @param {function()=} opt_callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.setProperties =
     function(guid, properties, opt_callback) {};
@@ -8908,6 +9398,7 @@
  * @param {!Object} properties
  * @param {function(string)=} opt_callback Returns guid of the configured
  *     configuration.
+ * @return {undefined}
  */
 chrome.networkingPrivate.createNetwork =
     function(shared, properties, opt_callback) {};
@@ -8916,6 +9407,7 @@
 /**
  * @param {string} guid
  * @param {function()=} opt_callback Called when the operation has completed.
+ * @return {undefined}
  */
 chrome.networkingPrivate.forgetNetwork = function(guid, opt_callback) {};
 
@@ -8924,6 +9416,7 @@
  * @param {!chrome.networkingPrivate.NetworkFilter} filter
  * @param {function(!Array<!chrome.networkingPrivate.NetworkStateProperties>)}
  *     callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getNetworks = function(filter, callback) {};
 
@@ -8933,6 +9426,7 @@
  * @param {function(!Array<!chrome.networkingPrivate.NetworkStateProperties>)}
  *      callback
  * @deprecated Use chrome.networkingPrivate.getNetworks with filter.visible=true
+ * @return {undefined}
  */
 chrome.networkingPrivate.getVisibleNetworks = function(type, callback) {};
 
@@ -8940,6 +9434,7 @@
 /**
  * @param {function(!Array<string>)} callback
  * @deprecated Use chrome.networkingPrivate.getDeviceStates.
+ * @return {undefined}
  */
 chrome.networkingPrivate.getEnabledNetworkTypes = function(callback) {};
 
@@ -8947,21 +9442,29 @@
 /**
  * @param {function(!Array<!chrome.networkingPrivate.DeviceStateProperties>)}
  *     callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getDeviceStates = function(callback) {};
 
 
-/** @param {string} networkType */
+/**
+ * @param {string} networkType
+ * @return {undefined}
+ */
 chrome.networkingPrivate.enableNetworkType = function(networkType) {};
 
 
-/** @param {string} networkType */
+/**
+ * @param {string} networkType
+ * @return {undefined}
+ */
 chrome.networkingPrivate.disableNetworkType = function(networkType) {};
 
 
 /**
  * Requests that the networking subsystem scan for new networks and update the
  * list returned by getVisibleNetworks.
+ * @return {undefined}
  */
 chrome.networkingPrivate.requestNetworkScan = function() {};
 
@@ -8969,6 +9472,7 @@
 /**
  * @param {string} guid
  * @param {function()=} opt_callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.startConnect = function(guid, opt_callback) {};
 
@@ -8976,6 +9480,7 @@
 /**
  * @param {string} guid
  * @param {function()=} opt_callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.startDisconnect = function(guid, opt_callback) {};
 
@@ -8984,6 +9489,7 @@
  * @param {string} guid
  * @param {(string|function())=} opt_carrierOrCallback
  * @param {function()=} opt_callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.startActivate =
     function(guid, opt_carrierOrCallback, opt_callback) {};
@@ -8992,6 +9498,7 @@
 /**
  * @param {!chrome.networkingPrivate.VerificationProperties} verificationInfo
  * @param {function(boolean)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.verifyDestination =
     function(verificationInfo, callback) {};
@@ -9001,6 +9508,7 @@
  * @param {!chrome.networkingPrivate.VerificationProperties} verificationInfo
  * @param {string} guid
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.verifyAndEncryptCredentials =
     function(verificationInfo, guid, callback) {};
@@ -9010,6 +9518,7 @@
  * @param {!chrome.networkingPrivate.VerificationProperties} verificationInfo
  * @param {string} data
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.verifyAndEncryptData =
     function(verificationInfo, data, callback) {};
@@ -9019,6 +9528,7 @@
  * @param {string} ipOrMacAddress
  * @param {boolean} enabled
  * @param {function(string)=} opt_callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.setWifiTDLSEnabledState =
     function(ipOrMacAddress, enabled, opt_callback) {};
@@ -9027,6 +9537,7 @@
 /**
  * @param {string} ipOrMacAddress
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getWifiTDLSStatus =
     function(ipOrMacAddress, callback) {};
@@ -9035,6 +9546,7 @@
 /**
  * @param {string} guid
  * @param {function(string)} callback
+ * @return {undefined}
  */
 chrome.networkingPrivate.getCaptivePortalStatus = function(guid, callback) {};
 
@@ -9099,12 +9611,16 @@
 /**
  * @param {function(!Array<!chrome.mdns.MdnsService>): void} callback
  * @param {!Object=} opt_filter
+ * @return {undefined}
  */
 chrome.mdns.ServiceListEvent.prototype.addListener =
     function(callback, opt_filter) {};
 
 
-/** @param {function(!Array<!chrome.mdns.MdnsService>): void} callback */
+/**
+ * @param {function(!Array<!chrome.mdns.MdnsService>): void} callback
+ * @return {undefined}
+ */
 chrome.mdns.ServiceListEvent.prototype.removeListener = function(callback) {};
 
 
@@ -9123,7 +9639,10 @@
 chrome.mdns.onServiceList;
 
 
-/** @param {function()} callback */
+/**
+ * @param {function()} callback
+ * @return {undefined}
+ */
 chrome.mdns.forceDiscovery = function(callback) {};
 
 
@@ -9158,6 +9677,7 @@
  * Returns the list of cloud devices visible locally or available in the
  * cloud for user account.
  * @param {function(!Array<!chrome.gcdPrivate.Device>): void} callback
+ * @return {undefined}
  */
 chrome.gcdPrivate.getCloudDeviceList = function(callback) {};
 
@@ -9166,6 +9686,7 @@
  * Queries network for local devices. Triggers onDeviceStateChanged and
  * onDeviceRemoved events. Call this function *only* after registering for
  * onDeviceStateChanged and onDeviceRemoved events, or it will do nothing.
+ * @return {undefined}
  */
 chrome.gcdPrivate.queryForNewLocalDevices = function() {};
 
@@ -9177,6 +9698,7 @@
  * with true if wifi password was cached and false if it was unavailable.
  * @param {string} ssid
  * @param {function(boolean): void} callback
+ * @return {undefined}
  */
 chrome.gcdPrivate.prefetchWifiPassword = function(ssid, callback) {};
 
@@ -9189,6 +9711,7 @@
  *     |status|: The status of the operation (success or type of error).
  *     |deviceInfo|: Content of /privet/info response.
  *     https://developers.google.com/cloud-devices/v1/reference/local-api/info
+ * @return {undefined}
  */
 chrome.gcdPrivate.getDeviceInfo = function(serviceName, callback) {};
 
@@ -9201,6 +9724,7 @@
  *     |sessionId|, is the session ID (identifies the session for future calls).
  *     2nd param, |status|, is the status (success or type of error). 3rd param,
  *     |pairingTypes|, is a list of pairing types supported by this device.
+ * @return {undefined}
  */
 chrome.gcdPrivate.createSession = function(serviceName, callback) {};
 
@@ -9210,6 +9734,7 @@
  * @param {number} sessionId
  * @param {string} pairingType
  * @param {function(string): void} callback
+ * @return {undefined}
  */
 chrome.gcdPrivate.startPairing = function(sessionId, pairingType, callback) {};
 
@@ -9219,6 +9744,7 @@
  * @param {number} sessionId
  * @param {string} code
  * @param {function(string): void} callback
+ * @return {undefined}
  */
 chrome.gcdPrivate.confirmCode = function(sessionId, code, callback) {};
 
@@ -9233,6 +9759,7 @@
  * @param {!Object} input The input message to be sent over the encrypted
  *     channel.
  * @param {function(string, ?Object): void} callback
+ * @return {undefined}
  */
 chrome.gcdPrivate.sendMessage = function(sessionId, api, input, callback) {};
 
@@ -9240,6 +9767,7 @@
 /**
  * Terminate the session with the device.
  * @param {number} sessionId
+ * @return {undefined}
  */
 chrome.gcdPrivate.terminateSession = function(sessionId) {};
 
@@ -9248,6 +9776,7 @@
  * Returns command definitions.
  * @param {string} deviceId The device to get command definitions for.
  * @param {function(!Object): void} callback The result callback.
+ * @return {undefined}
  */
 chrome.gcdPrivate.getCommandDefinitions = function(deviceId, callback) {};
 
@@ -9262,6 +9791,7 @@
  * @param {!Object} command Described at
  *     https://developers.google.com/cloud-devices/v1/reference/commands.
  * @param {function(!Object): void} callback  The result callback.
+ * @return {undefined}
  */
 chrome.gcdPrivate.insertCommand = function(
     deviceId, expireInMs, command, callback) {};
@@ -9271,6 +9801,7 @@
  * Returns a particular command.
  * @param {string} commandId Unique command ID.
  * @param {function(!Object): void} callback  The result callback.
+ * @return {undefined}
  */
 chrome.gcdPrivate.getCommand = function(commandId, callback) {};
 
@@ -9279,6 +9810,7 @@
  * Cancels a command.
  * @param {string} commandId Unique command ID.
  * @param {function(!Object): void} callback  The result callback.
+ * @return {undefined}
  */
 chrome.gcdPrivate.cancelCommand = function(commandId, callback) {};
 
@@ -9290,6 +9822,7 @@
  *     value 'me' can be used to list by the current user.
  * @param {string} state Command state.
  * @param {function(!Array<!Object>): void} callback  The result callback.
+ * @return {undefined}
  */
 chrome.gcdPrivate.getCommandsList = function(
     deviceId, byUser, state, callback) {};
@@ -9303,11 +9836,17 @@
 chrome.gcdPrivate.DeviceEvent = function() {};
 
 
-/** @param {function(!chrome.gcdPrivate.Device): void} callback */
+/**
+ * @param {function(!chrome.gcdPrivate.Device): void} callback
+ * @return {undefined}
+ */
 chrome.gcdPrivate.DeviceEvent.prototype.addListener = function(callback) {};
 
 
-/** @param {function(!chrome.gcdPrivate.Device): void} callback */
+/**
+ * @param {function(!chrome.gcdPrivate.Device): void} callback
+ * @return {undefined}
+ */
 chrome.gcdPrivate.DeviceEvent.prototype.removeListener = function(callback) {};
 
 
@@ -9396,6 +9935,7 @@
 /**
  * @param {!chrome.bluetoothPrivate.NewAdapterState} adapterState
  * @param {function()} callback
+ * @return {undefined}
  */
 chrome.bluetoothPrivate.setAdapterState = function(adapterState, callback) {};
 
@@ -9403,10 +9943,37 @@
 /**
  * @param {!chrome.bluetoothPrivate.SetPairingResponseOptions} options
  * @param {function()} callback
+ * @return {undefined}
  */
 chrome.bluetoothPrivate.setPairingResponse = function(options, callback) {};
 
 
+/**
+ * @param {string} deviceAddress
+ * @param {function():void=} callback
+ * @return {undefined}
+ */
+chrome.bluetoothPrivate.forgetDevice = function(deviceAddress, callback) {};
+
+
+/**
+ * @typedef {{
+ *   transport: (!chrome.bluetoothPrivate.TransportType|undefined),
+ *   uuids: ((string|!Array<string>)|undefined),
+ *   rssi: (number|undefined),
+ *   pathloss: (number|undefined)
+ * }}
+ */
+chrome.bluetoothPrivate.DiscoveryFilter;
+
+
+/**
+ * Set or clear discovery filter.
+ * @param {!chrome.bluetoothPrivate.DiscoveryFilter} discoveryFilter
+ * @param {function():void=} callback
+ */
+chrome.bluetoothPrivate.setDiscoveryFilter = function(discoveryFilter, callback) {};
+
 
 /**
  * Event whose listeners take a PairingEvent parameter.
@@ -9415,12 +9982,18 @@
 chrome.bluetoothPrivate.PairingEventEvent = function() {};
 
 
-/** @param {function(!chrome.bluetoothPrivate.PairingEvent): void} callback */
+/**
+ * @param {function(!chrome.bluetoothPrivate.PairingEvent): void} callback
+ * @return {undefined}
+ */
 chrome.bluetoothPrivate.PairingEventEvent.prototype.addListener =
     function(callback) {};
 
 
-/** @param {function(!chrome.bluetoothPrivate.PairingEvent): void} callback */
+/**
+ * @param {function(!chrome.bluetoothPrivate.PairingEvent): void} callback
+ * @return {undefined}
+ */
 chrome.bluetoothPrivate.PairingEventEvent.prototype.removeListener =
     function(callback) {};
 
@@ -9525,6 +10098,7 @@
  * @param {function(string, string): void=} opt_callback Response callback that
  *     returns two string: (1) an error string (or empty string on success) and
  *     (2) an error code in case of error
+ * @return {undefined}
  */
 chrome.inlineInstallPrivate.install = function(id, opt_callback) {};
 
@@ -9558,6 +10132,481 @@
  *    thumbnail: (boolean|undefined)
  *  }} details
  * @param {function(ArrayBuffer=)} callback
+ * @return {undefined}
  * @see https://goo.gl/7dvJFW#method-setWallpaper
  */
- chrome.wallpaper.setWallpaper = function(details, callback) {};
+chrome.wallpaper.setWallpaper = function(details, callback) {};
+
+
+/**
+ * @const
+ * @see https://developer.chrome.com/extensions/downloads
+ */
+chrome.downloads = {};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/downloads#type-FilenameConflictAction
+ */
+chrome.downloads.FilenameConflictAction = {
+  UNIQUIFY: '',
+  OVERWRITE: '',
+  PROMPT: ''
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/downloads#type-InterruptReason
+ */
+chrome.downloads.InterruptReason = {
+  FILE_FAILED: '',
+  FILE_ACCESS_DENIED: '',
+  FILE_NO_SPACE: '',
+  FILE_NAME_TOO_LONG: '',
+  FILE_TOO_LARGE: '',
+  FILE_VIRUS_INFECTED: '',
+  FILE_TRANSIENT_ERROR: '',
+  FILE_BLOCKED: '',
+  FILE_SECURITY_CHECK_FAILED: '',
+  FILE_TOO_SHORT: '',
+  FILE_HASH_MISMATCH: '',
+  NETWORK_FAILED: '',
+  NETWORK_TIMEOUT: '',
+  NETWORK_DISCONNECTED: '',
+  NETWORK_SERVER_DOWN: '',
+  NETWORK_INVALID_REQUEST: '',
+  SERVER_FAILED: '',
+  SERVER_NO_RANGE: '',
+  SERVER_BAD_CONTENT: '',
+  SERVER_UNAUTHORIZED: '',
+  SERVER_CERT_PROBLEM: '',
+  SERVER_FORBIDDEN: '',
+  SERVER_UNREACHABLE: '',
+  USER_CANCELED: '',
+  USER_SHUTDOWN: '',
+  CRASH: '',
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/downloads#type-DangerType
+ */
+chrome.downloads.DangerType = {
+  FILE: '',
+  URL: '',
+  CONTENT: '',
+  UNCOMMON: '',
+  HOST: '',
+  UNWANTED: '',
+  SAFE: '',
+  ACCEPTED: ''
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/downloads#type-State
+ */
+chrome.downloads.State = {
+  IN_PROGRESS: '',
+  INTERRUPTED: '',
+  COMPLETE: '',
+};
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#type-DownloadItem
+ */
+chrome.downloads.DownloadItem = function() {};
+
+
+/** @type {number} */
+chrome.downloads.DownloadItem.prototype.id;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.url;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.finalUrl;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.referrer;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.filename;
+
+
+/** @type {boolean} */
+chrome.downloads.DownloadItem.prototype.incognito;
+
+
+/** @type {!chrome.downloads.DangerType|string} */
+chrome.downloads.DownloadItem.prototype.dangerType;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.mime;
+
+
+/** @type {string} */
+chrome.downloads.DownloadItem.prototype.startTime;
+
+
+/** @type {?string} */
+chrome.downloads.DownloadItem.prototype.endTime;
+
+
+/** @type {?string} */
+chrome.downloads.DownloadItem.prototype.estimatedEndTime;
+
+
+/** @type {!chrome.downloads.State|string} */
+chrome.downloads.DownloadItem.prototype.state;
+
+
+/** @type {boolean} */
+chrome.downloads.DownloadItem.prototype.paused;
+
+
+/** @type {boolean} */
+chrome.downloads.DownloadItem.prototype.canResume;
+
+
+/** @type {!chrome.downloads.InterruptReason|string|undefined} */
+chrome.downloads.DownloadItem.prototype.error;
+
+
+/** @type {number} */
+chrome.downloads.DownloadItem.prototype.bytesReceived;
+
+
+/** @type {number} */
+chrome.downloads.DownloadItem.prototype.totalBytes;
+
+
+/** @type {number} */
+chrome.downloads.DownloadItem.prototype.fileSize;
+
+
+/** @type {boolean} */
+chrome.downloads.DownloadItem.prototype.exists;
+
+
+/** @type {?string} */
+chrome.downloads.DownloadItem.prototype.byExtensionId;
+
+
+/** @type {?string} */
+chrome.downloads.DownloadItem.prototype.byExtensionName;
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#type-StringDelta
+ */
+chrome.downloads.StringDelta = function() {};
+
+
+/** @type {?string} */
+chrome.downloads.StringDelta.prototype.previous;
+
+
+/** @type {?string} */
+chrome.downloads.StringDelta.prototype.current;
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#type-DoubleDelta
+ */
+chrome.downloads.DoubleDelta = function() {};
+
+
+/** @type {?number} */
+chrome.downloads.DoubleDelta.prototype.previous;
+
+
+/** @type {?number} */
+chrome.downloads.DoubleDelta.prototype.current;
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#type-BooleanDelta
+ */
+chrome.downloads.BooleanDelta = function() {};
+
+
+/** @type {?boolean} */
+chrome.downloads.BooleanDelta.prototype.previous;
+
+
+/** @type {?boolean} */
+chrome.downloads.BooleanDelta.prototype.current;
+
+
+/**
+ * @param {{
+ *    url: string,
+ *    filename: (string|undefined),
+ *    conflictAction:
+ *        (!chrome.downloads.FilenameConflictAction|string|undefined),
+ *    saveAs: (boolean|undefined),
+ *    method: (string|undefined),
+ *    headers: (!Array<{name:string, value:string}>|undefined),
+ *    body: (string|undefined)
+ *  }} details
+ * @param {function(number)=} opt_callback
+ * @see https://developer.chrome.com/extensions/downloads#method-download
+ */
+chrome.downloads.download = function(details, opt_callback) {};
+
+
+/**
+ * @typedef {?{
+ *    query: (!Array<string>|undefined),
+ *    startedBefore: (string|undefined),
+ *    startedAfter: (string|undefined),
+ *    endedBefore: (string|undefined),
+ *    endedAfter: (string|undefined),
+ *    totalBytesGreater: (number|undefined),
+ *    totalBytesLess: (number|undefined),
+ *    filenameRegex: (string|undefined),
+ *    urlRegex: (string|undefined),
+ *    finalUrlRegex: (string|undefined),
+ *    limit: (number|undefined),
+ *    orderedBy: (!Array<string>|undefined),
+ *    id: (number|undefined),
+ *    url: (string|undefined),
+ *    finalUrl: (string|undefined),
+ *    filename: (string|undefined),
+ *    danger: (!chrome.downloads.DangerType|string|undefined),
+ *    mime: (string|undefined),
+ *    startTime: (string|undefined),
+ *    endTime: (string|undefined),
+ *    state: (!chrome.downloads.State|string|undefined),
+ *    paused: (boolean|undefined),
+ *    error: (!chrome.downloads.InterruptReason|string|undefined),
+ *    bytesReceived: (number|undefined),
+ *    totalBytes: (number|undefined),
+ *    fileSize: (number|undefined),
+ *    exists: (boolean|undefined)
+ *  }}
+ */
+chrome.downloads.Query;
+
+
+/**
+ * @param {!chrome.downloads.Query} query
+ * @param {function(!Array<!chrome.downloads.DownloadItem>)} callback
+ * @see https://developer.chrome.com/extensions/downloads#method-search
+ */
+chrome.downloads.search = function(query, callback) {};
+
+
+/**
+ * @param {number} id
+ * @param {function()=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-pause
+ */
+chrome.downloads.pause = function(id, opt_callback) {};
+
+
+/**
+ * @param {string} id
+ * @param {function()=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-resume
+ */
+chrome.downloads.resume = function(id, opt_callback) {};
+
+
+/**
+ * @param {number} id
+ * @param {function()=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-cancel
+ */
+chrome.downloads.cancel = function(id, opt_callback) {};
+
+
+/**
+ * @param {number} id
+ * @param {{size:(number|undefined)}|function(string)} optionsOrCallback
+ * @param {function(string)=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-getFileIcon
+ */
+chrome.downloads.getFileIcon = function(id, optionsOrCallback, opt_callback) {};
+
+
+/**
+ * @param {number} id
+ * @see https://developer.chrome.com/extensions/downloads#method-open
+ */
+chrome.downloads.open = function(id) {};
+
+
+/**
+ * @param {number} id
+ * @see https://developer.chrome.com/extensions/downloads#method-show
+ */
+chrome.downloads.show = function(id) {};
+
+
+/** @see https://developer.chrome.com/extensions/downloads#method-showDefaultFolder */
+chrome.downloads.showDefaultFolder = function() {};
+
+
+/**
+ * @param {!chrome.downloads.Query} query
+ * @param {function(!Array<number>)} callback
+ * @see https://developer.chrome.com/extensions/downloads#method-erase
+ */
+chrome.downloads.erase = function(query, callback) {};
+
+
+/**
+ * @param {number} id
+ * @param {function()=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-removeFile
+ */
+chrome.downloads.removeFile = function(id, opt_callback) {};
+
+
+/**
+ * @param {number} id
+ * @param {function()=} opt_callback
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-acceptDanger
+ */
+chrome.downloads.acceptDanger = function(id, opt_callback) {};
+
+
+/**
+ * @param {number} id
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-drag
+ */
+chrome.downloads.drag = function(id) {};
+
+
+/**
+ * @param {boolean} enabled
+ *
+ * @see https://developer.chrome.com/extensions/downloads#method-setShelfEnabled
+ */
+chrome.downloads.setShelfEnabled = function(enabled) {};
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#event-onCreated
+ */
+chrome.downloads.CreatedEvent = function() {};
+
+
+/** @param {function(!chrome.downloads.DownloadItem)} callback */
+chrome.downloads.CreatedEvent.prototype.addListener = function(callback) {};
+
+
+/** @param {function(!chrome.downloads.DownloadItem)} callback */
+chrome.downloads.CreatedEvent.prototype.removeListener =
+    function(callback) {};
+
+
+/**
+ * @param {function(!chrome.downloads.DownloadItem)} callback
+ * @return {boolean}
+ */
+chrome.downloads.CreatedEvent.prototype.hasListener = function(callback) {};
+
+
+/** @return {boolean} */
+chrome.downloads.CreatedEvent.prototype.hasListeners = function() {};
+
+
+/**
+ * @type {!chrome.downloads.CreatedEvent}
+ * https://developer.chrome.com/extensions/downloads#event-onCreated
+ */
+chrome.downloads.onCreated;
+
+
+/**
+ * @type {!ChromeNumberEvent}
+ * https://developer.chrome.com/extensions/downloads#event-onErased
+ */
+chrome.downloads.onErased;
+
+
+/**
+ * @type {!ChromeObjectEvent}
+ * https://developer.chrome.com/extensions/downloads#event-onChanged
+ */
+chrome.downloads.onChanged;
+
+
+/**
+ * @typedef {?{
+ *  filename: string,
+ *  conflictAction: (!chrome.downloads.FilenameConflictAction|undefined)
+ * }}
+ * @see https://developer.chrome.com/extensions/downloads#event-onDeterminingFilename
+ */
+chrome.downloads.FilenameSuggestion;
+
+
+/**
+ * @constructor
+ * @see https://developer.chrome.com/extensions/downloads#event-onDeterminingFilename
+ */
+chrome.downloads.DeterminingFilenameEvent = function() {};
+
+
+/**
+ * @param {function(
+ *     !chrome.downloads.DownloadItem,
+ *     function(!chrome.downloads.FilenameSuggestion=))} callback */
+chrome.downloads.DeterminingFilenameEvent.prototype.addListener =
+    function(callback) {};
+
+
+/**
+ * @param {function(
+ *     !chrome.downloads.DownloadItem,
+ *     function(!chrome.downloads.FilenameSuggestion=))} callback */
+chrome.downloads.DeterminingFilenameEvent.prototype.removeListener =
+    function(callback) {};
+
+
+/**
+ * @param {function(
+ *     !chrome.downloads.DownloadItem,
+ *     function(!chrome.downloads.FilenameSuggestion=))} callback
+ * @return {boolean}
+ */
+chrome.downloads.DeterminingFilenameEvent.prototype.hasListener =
+    function(callback) {};
+
+
+/** @return {boolean} */
+chrome.downloads.DeterminingFilenameEvent.prototype.hasListeners =
+    function() {};
+
+
+/**
+ * @type {!chrome.downloads.DeterminingFilenameEvent}
+ * https://developer.chrome.com/extensions/downloads#event-onDeterminingFilename
+ */
+chrome.downloads.onDeterminingFilename;
diff --git a/third_party/closure_compiler/externs/polymer-1.0.js b/third_party/closure_compiler/externs/polymer-1.0.js
index 7b4101f..6d6acb6 100644
--- a/third_party/closure_compiler/externs/polymer-1.0.js
+++ b/third_party/closure_compiler/externs/polymer-1.0.js
@@ -44,6 +44,10 @@
  * additional IP rights grant found at http://polymer.github.io/PATENTS.txt.
  */
 
+if (Math.random() < 1) {
+  throw "polymer externs should not be executed";
+}
+
 /**
  * @param {!{is: string}} descriptor The Polymer descriptor of the element.
  * @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#custom-element-registration
@@ -868,6 +872,13 @@
 PolymerDomApi.prototype.queryDistributedElements = function(selector) {};
 
 /**
+ * Returns a list of effective child nodes for this element.
+ *
+ * @return {!Array<!HTMLElement>}
+ */
+PolymerDomApi.prototype.getEffectiveChildNodes = function() {};
+
+/**
  * A Polymer Event API.
  *
  * @constructor
@@ -1156,6 +1167,16 @@
 DomRepeatElement.prototype.indexForElement = function(el) {};
 
 
+/**
+ * Count of currently rendered items after `filter` (if any) has been applied.
+ * If "chunking mode" is enabled, `renderedItemCount` is updated each time a
+ * set of template instances is rendered.
+ *
+ * @type {number}
+ */
+DomRepeatElement.prototype.renderedItemCount;
+
+
 
 /**
  * @see https://github.com/Polymer/polymer/blob/master/src/lib/template/array-selector.html
@@ -1342,3 +1363,83 @@
  * @param {...*} args The function arguments.
  */
 Polymer.RenderStatus.afterNextRender = function(element, fn, args) {}
+
+
+
+/**
+ * Static analysis for Polymer.
+ * @type {!Object}
+ */
+var hydrolysis = {};
+
+/**
+ * A database of Polymer metadata defined in HTML
+ * @param {boolean} attachAST If true, attach a parse5 compliant AST
+ * @param {Object=} opt_loader An optional FileLoader used to load
+ * external resources
+ */
+hydrolysis.Analyzer = function(attachAST, opt_loader) {};
+
+
+/**
+ * Shorthand for transitively loading and processing all imports
+ * beginning at href.
+ * @param {string} href The root import to begin loading from.
+ * @param {Object=} opt_options Any additional options for the load.
+ */
+hydrolysis.Analyzer.analyze = function(href, opt_options) {};
+
+
+
+/**
+ * Contains information useful for debugging. Should not be used in production
+ * code and the API may change on short notice.
+ * @type {!Object}
+ */
+Polymer.telemetry;
+
+/**
+ * Number of elements instantiated so far.
+ * @type {number}
+ */
+Polymer.telemetry.instanceCount;
+
+/**
+ * Array of all registered element prototypes. Being prototypes, not all runtime
+ * properties will be available, but eg. `is` is always there.
+ * @type {!Array<!PolymerElement>}
+ */
+Polymer.telemetry.registrations;
+
+Polymer.AppLayout;
+
+/** @constructor */
+Polymer.AppLayout.LocalDomWithBackground = function(){};
+/** @type {!HTMLElement} */
+Polymer.AppLayout.LocalDomWithBackground.prototype.backgroundFrontLayer;
+/** @type {!HTMLElement} */
+Polymer.AppLayout.LocalDomWithBackground.prototype.backgroundRearLayer;
+/** @type {!HTMLElement} */
+Polymer.AppLayout.LocalDomWithBackground.prototype.background;
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ */
+Polymer.AppLayout.ElementWithBackground = function(){};
+
+// TODO(ajo): Follow up with app-layout team and remove private api from this prototype
+Polymer.AppLayout.ElementWithBackground.prototype = {
+  /** @type {!Polymer.AppLayout.LocalDomWithBackground} */
+  $: null,
+  /** @return {boolean} True if there's content below the current element */
+  isContentBelow: function(){},
+  /** Updates the elements scroll state */
+  _updateScrollState: function(){},
+  /** @return {boolean} true if the element is on screen */
+  isOnScreen: function(){},
+  /** @type {number} Internal bookkeeping to track screen position */
+  _deltaHeight: 0,
+  /** @return {?Element} Element in local dom by id. */
+  _getDOMRef: function(title){}
+}
diff --git a/third_party/closure_compiler/runner/runner.jar b/third_party/closure_compiler/runner/runner.jar
index e6e6091..5e12b00 100644
--- a/third_party/closure_compiler/runner/runner.jar
+++ b/third_party/closure_compiler/runner/runner.jar
Binary files differ
diff --git a/third_party/dpkg-dev/LICENSE b/third_party/dpkg-dev/LICENSE
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/third_party/dpkg-dev/LICENSE
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/third_party/dpkg-dev/OWNERS b/third_party/dpkg-dev/OWNERS
new file mode 100644
index 0000000..6204ee1
--- /dev/null
+++ b/third_party/dpkg-dev/OWNERS
@@ -0,0 +1,3 @@
+thomasanderson@chromium.org
+thestig@chromium.org
+sbc@chromium.org
diff --git a/third_party/dpkg-dev/README.chromium b/third_party/dpkg-dev/README.chromium
new file mode 100644
index 0000000..56e1186b
--- /dev/null
+++ b/third_party/dpkg-dev/README.chromium
@@ -0,0 +1,23 @@
+Name: dpkg-dev
+URL: http://packages.ubuntu.com/source/trusty/dpkg
+Version: 1.17.5ubuntu5.6
+Date: October 13, 2016
+License: GPL v2
+License File: NOT_SHIPPED
+Security Critical: no
+
+Description:
+Dpkg is a package manager for Debian.  The component necessary for
+development is dpkg-shlibdeps which calculates dependency information
+for executables.
+
+This should only be added during the infra transition from Precise to
+Trusty, and should be removed once complete.  See bug 655755 for
+details.
+
+How to update:
+1. Acquire the dpkg-dev source: "apt-get source dpkg-dev".
+2. Remove all but *table and scripts/ from the top-level dirctory.
+3. Remove all but "Dpkg", "Dpkg.pm", and "dpkg-shlibdeps.pl" from scripts/
+
+Local Modifications: Removed all unnecessary utilities.
diff --git a/third_party/dpkg-dev/abitable b/third_party/dpkg-dev/abitable
new file mode 100644
index 0000000..300a1ab
--- /dev/null
+++ b/third_party/dpkg-dev/abitable
@@ -0,0 +1,11 @@
+# This file contains the table of arch ABI attribute overrides.
+#
+# If the ABI is not present here then the attribute information for a
+# Debian triplet matches the one on the cputable.
+#
+# Column 1 is the Debian name for the ABI.
+# Column 2 is the size (in bits) of the ABI integers/pointers.
+#
+# <Debian name>	<Bits>
+gnuabin32	32
+gnux32		32
diff --git a/third_party/dpkg-dev/cputable b/third_party/dpkg-dev/cputable
new file mode 100644
index 0000000..fb5d2764
--- /dev/null
+++ b/third_party/dpkg-dev/cputable
@@ -0,0 +1,42 @@
+# This file contains the table of known CPU names.
+#
+# Architecture names are formed as a combination of the system name
+# (from ostable) and CPU name (from this table) after mapping from
+# the Debian triplet (from triplettable).
+#
+# Column 1 is the Debian name for the CPU, used to form the cpu part in
+# the Debian triplet.
+# Column 2 is the GNU name for the CPU, used to output build and host
+# targets in ‘dpkg-architecture’.
+# Column 3 is an extended regular expression used to match against the
+# CPU part of the output of the GNU config.guess script.
+# Column 4 is the size (in bits) of the integers/pointers
+# Column 5 is the endianness (byte ordering in numbers)
+#
+# <Debian name>	<GNU name>	<config.guess regex>	<Bits>	<Endianness>
+i386		i486		(i[3456]86|pentium)	32	little
+ia64		ia64		ia64			64	little
+alpha		alpha		alpha.*			64	little
+amd64		x86_64		x86_64			64	little
+armeb		armeb		arm.*b			32	big
+arm		arm		arm.*			32	little
+arm64		aarch64		aarch64			64	little
+avr32		avr32		avr32			32	big
+hppa		hppa		hppa.*			32	big
+m32r		m32r		m32r			32	big
+m68k		m68k		m68k			32	big
+mips		mips		mips(eb)?		32	big
+mipsel		mipsel		mipsel			32	little
+mips64		mips64		mips64			64	big
+mips64el	mips64el	mips64el		64	little
+powerpc		powerpc		(powerpc|ppc)		32	big
+ppc64		powerpc64	(powerpc|ppc)64		64	big
+ppc64el		powerpc64le	powerpc64le		64	little
+s390		s390		s390			32	big
+s390x		s390x		s390x			64	big
+sh3		sh3		sh3			32	little
+sh3eb		sh3eb		sh3eb			32	big
+sh4		sh4		sh4			32	little
+sh4eb		sh4eb		sh4eb			32	big
+sparc		sparc		sparc			32	big
+sparc64		sparc64		sparc64			64	big
diff --git a/third_party/dpkg-dev/ostable b/third_party/dpkg-dev/ostable
new file mode 100644
index 0000000..29d3843
--- /dev/null
+++ b/third_party/dpkg-dev/ostable
@@ -0,0 +1,38 @@
+# This file contains the table of known operating system names.
+#
+# Architecture names are formed as a combination of the system name
+# (from this table) and CPU name (from cputable) after mapping from
+# the Debian triplet (from triplettable).
+#
+# Column 1 is the Debian name for the system, used to form the system part
+# in the Debian triplet.
+# Column 2 is the GNU name for the system, used to output build and host
+# targets in ‘dpkg-architecture’.
+# Column 3 is an extended regular expression used to match against the
+# system part of the output of the GNU config.guess script.
+#
+# <Debian name>		<GNU name>		<config.guess regex>
+uclibceabi-linux	linux-uclibceabi	linux[^-]*-uclibceabi
+uclibc-linux		linux-uclibc		linux[^-]*-uclibc
+musleabihf-linux	linux-musleabihf	linux[^-]*-musleabihf
+musl-linux		linux-musl		linux[^-]*-musl
+gnueabihf-linux		linux-gnueabihf		linux[^-]*-gnueabihf
+gnueabi-linux		linux-gnueabi		linux[^-]*-gnueabi
+gnuabin32-linux		linux-gnuabin32		linux[^-]*-gnuabin32
+gnuabi64-linux		linux-gnuabi64		linux[^-]*-gnuabi64
+gnuspe-linux		linux-gnuspe		linux[^-]*-gnuspe
+gnux32-linux		linux-gnux32		linux[^-]*-gnux32
+gnulp-linux		linux-gnulp		linux[^-]*-gnulp
+gnu-linux		linux-gnu		linux[^-]*(-gnu.*)?
+gnu-kfreebsd		kfreebsd-gnu		kfreebsd[^-]*(-gnu.*)?
+gnu-knetbsd		knetbsd-gnu		knetbsd[^-]*(-gnu.*)?
+gnu-kopensolaris	kopensolaris-gnu	kopensolaris[^-]*(-gnu.*)?
+gnu-hurd		gnu			gnu[^-]*
+bsd-darwin		darwin			darwin[^-]*
+bsd-freebsd		freebsd			freebsd[^-]*
+bsd-netbsd		netbsd			netbsd[^-]*
+bsd-openbsd		openbsd			openbsd[^-]*
+sysv-solaris		solaris			solaris[^-]*
+uclibceabi-uclinux	uclinux-uclibceabi	uclinux[^-]*-uclibceabi
+uclibc-uclinux		uclinux-uclibc		uclinux[^-]*(-uclibc.*)?
+tos-mint		mint			mint[^-]*
diff --git a/third_party/dpkg-dev/scripts/Dpkg.pm b/third_party/dpkg-dev/scripts/Dpkg.pm
new file mode 100644
index 0000000..2dd93989
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg.pm
@@ -0,0 +1,43 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Exporter qw(import);
+our @EXPORT_OK = qw($PROGNAME $PROGVERSION $CONFDIR $ADMINDIR $LIBDIR $DATADIR);
+our @EXPORT = qw($version $progname $admindir $dpkglibdir $pkgdatadir);
+
+our ($PROGNAME) = $0 =~ m{(?:.*/)?([^/]*)};
+
+# The following lines are automatically fixed at install time
+our $PROGVERSION = '1.17.x';
+our $CONFDIR = '/etc/dpkg';
+our $ADMINDIR = '/var/lib/dpkg';
+our $LIBDIR = '.';
+our $DATADIR = '..';
+$DATADIR = $ENV{DPKG_DATADIR} if defined $ENV{DPKG_DATADIR};
+
+# XXX: Backwards compatibility, to be removed on VERSION 2.00.
+## no critic (Variables::ProhibitPackageVars)
+our $version = $PROGVERSION;
+our $admindir = $ADMINDIR;
+our $dpkglibdir = $LIBDIR;
+our $pkgdatadir = $DATADIR;
+## use critic
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Arch.pm b/third_party/dpkg-dev/scripts/Dpkg/Arch.pm
new file mode 100644
index 0000000..33a669d8
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Arch.pm
@@ -0,0 +1,445 @@
+# Copyright © 2006-2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Arch;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(get_raw_build_arch get_raw_host_arch
+                    get_build_arch get_host_arch get_gcc_host_gnu_type
+                    get_valid_arches debarch_eq debarch_is debarch_is_wildcard
+                    debarch_to_cpuattrs
+                    debarch_to_gnutriplet gnutriplet_to_debarch
+                    debtriplet_to_gnutriplet gnutriplet_to_debtriplet
+                    debtriplet_to_debarch debarch_to_debtriplet
+                    gnutriplet_to_multiarch debarch_to_multiarch);
+
+use POSIX qw(:errno_h);
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Util qw(:list);
+use Dpkg::BuildEnv;
+
+my (@cpu, @os);
+my (%cputable, %ostable);
+my (%cputable_re, %ostable_re);
+my (%cpubits, %cpuendian);
+my %abibits;
+
+my %debtriplet_to_debarch;
+my %debarch_to_debtriplet;
+
+{
+    my $build_arch;
+    my $host_arch;
+    my $gcc_host_gnu_type;
+
+    sub get_raw_build_arch()
+    {
+	return $build_arch if defined $build_arch;
+
+	# Note: We *always* require an installed dpkg when inferring the
+	# build architecture. The bootstrapping case is handled by
+	# dpkg-architecture itself, by avoiding computing the DEB_BUILD_
+	# variables when they are not requested.
+
+	$build_arch = `dpkg --print-architecture`;
+	syserr('dpkg --print-architecture failed') if $? >> 8;
+
+	chomp $build_arch;
+	return $build_arch;
+    }
+
+    sub get_build_arch()
+    {
+	return Dpkg::BuildEnv::get('DEB_BUILD_ARCH') || get_raw_build_arch();
+    }
+
+    sub get_gcc_host_gnu_type()
+    {
+	return $gcc_host_gnu_type if defined $gcc_host_gnu_type;
+
+	$gcc_host_gnu_type = `\${CC:-gcc} -dumpmachine`;
+	if ($? >> 8) {
+	    $gcc_host_gnu_type = '';
+	} else {
+	    chomp $gcc_host_gnu_type;
+	}
+
+	return $gcc_host_gnu_type;
+    }
+
+    sub get_raw_host_arch()
+    {
+	return $host_arch if defined $host_arch;
+
+	$gcc_host_gnu_type = get_gcc_host_gnu_type();
+
+	if ($gcc_host_gnu_type eq '') {
+	    warning(_g("couldn't determine gcc system type, falling back to " .
+	               'default (native compilation)'));
+	} else {
+	    my (@host_archtriplet) = gnutriplet_to_debtriplet($gcc_host_gnu_type);
+	    $host_arch = debtriplet_to_debarch(@host_archtriplet);
+
+	    if (defined $host_arch) {
+		$gcc_host_gnu_type = debtriplet_to_gnutriplet(@host_archtriplet);
+	    } else {
+		warning(_g('unknown gcc system type %s, falling back to ' .
+		           'default (native compilation)'), $gcc_host_gnu_type);
+		$gcc_host_gnu_type = '';
+	    }
+	}
+
+	if (!defined($host_arch)) {
+	    # Switch to native compilation.
+	    $host_arch = get_raw_build_arch();
+	}
+
+	return $host_arch;
+    }
+
+    sub get_host_arch()
+    {
+	return Dpkg::BuildEnv::get('DEB_HOST_ARCH') || get_raw_host_arch();
+    }
+}
+
+sub get_valid_arches()
+{
+    read_cputable();
+    read_ostable();
+
+    my @arches;
+
+    foreach my $os (@os) {
+	foreach my $cpu (@cpu) {
+	    my $arch = debtriplet_to_debarch(split(/-/, $os, 2), $cpu);
+	    push @arches, $arch if defined($arch);
+	}
+    }
+
+    return @arches;
+}
+
+my $cputable_loaded = 0;
+sub read_cputable
+{
+    return if ($cputable_loaded);
+
+    local $_;
+    local $/ = "\n";
+
+    open my $cputable_fh, '<', "$Dpkg::DATADIR/cputable"
+	or syserr(_g('cannot open %s'), 'cputable');
+    while (<$cputable_fh>) {
+	if (m/^(?!\#)(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
+	    $cputable{$1} = $2;
+	    $cputable_re{$1} = $3;
+	    $cpubits{$1} = $4;
+	    $cpuendian{$1} = $5;
+	    push @cpu, $1;
+	}
+    }
+    close $cputable_fh;
+
+    $cputable_loaded = 1;
+}
+
+my $ostable_loaded = 0;
+sub read_ostable
+{
+    return if ($ostable_loaded);
+
+    local $_;
+    local $/ = "\n";
+
+    open my $ostable_fh, '<', "$Dpkg::DATADIR/ostable"
+	or syserr(_g('cannot open %s'), 'ostable');
+    while (<$ostable_fh>) {
+	if (m/^(?!\#)(\S+)\s+(\S+)\s+(\S+)/) {
+	    $ostable{$1} = $2;
+	    $ostable_re{$1} = $3;
+	    push @os, $1;
+	}
+    }
+    close $ostable_fh;
+
+    $ostable_loaded = 1;
+}
+
+my $abitable_loaded = 0;
+sub abitable_load()
+{
+    return if ($abitable_loaded);
+
+    local $_;
+    local $/ = "\n";
+
+    # Because the abitable is only for override information, do not fail if
+    # it does not exist, as that will only mean the other tables do not have
+    # an entry needing to be overridden. This way we do not require a newer
+    # dpkg by libdpkg-perl.
+    if (open my $abitable_fh, '<', "$Dpkg::DATADIR/abitable") {
+        while (<$abitable_fh>) {
+            if (m/^(?!\#)(\S+)\s+(\S+)/) {
+                $abibits{$1} = $2;
+            }
+        }
+        close $abitable_fh;
+    } elsif ($! != ENOENT) {
+        syserr(_g('cannot open %s'), 'abitable');
+    }
+
+    $abitable_loaded = 1;
+}
+
+my $triplettable_loaded = 0;
+sub read_triplettable()
+{
+    return if ($triplettable_loaded);
+
+    read_cputable();
+
+    local $_;
+    local $/ = "\n";
+
+    open my $triplettable_fh, '<', "$Dpkg::DATADIR/triplettable"
+	or syserr(_g('cannot open %s'), 'triplettable');
+    while (<$triplettable_fh>) {
+	if (m/^(?!\#)(\S+)\s+(\S+)/) {
+	    my $debtriplet = $1;
+	    my $debarch = $2;
+
+	    if ($debtriplet =~ /<cpu>/) {
+		foreach my $_cpu (@cpu) {
+		    (my $dt = $debtriplet) =~ s/<cpu>/$_cpu/;
+		    (my $da = $debarch) =~ s/<cpu>/$_cpu/;
+
+		    next if exists $debarch_to_debtriplet{$da}
+		         or exists $debtriplet_to_debarch{$dt};
+
+		    $debarch_to_debtriplet{$da} = $dt;
+		    $debtriplet_to_debarch{$dt} = $da;
+		}
+	    } else {
+		$debarch_to_debtriplet{$2} = $1;
+		$debtriplet_to_debarch{$1} = $2;
+	    }
+	}
+    }
+    close $triplettable_fh;
+
+    $triplettable_loaded = 1;
+}
+
+sub debtriplet_to_gnutriplet(@)
+{
+    my ($abi, $os, $cpu) = @_;
+
+    read_cputable();
+    read_ostable();
+
+    return unless defined($abi) && defined($os) && defined($cpu) &&
+        exists($cputable{$cpu}) && exists($ostable{"$abi-$os"});
+    return join('-', $cputable{$cpu}, $ostable{"$abi-$os"});
+}
+
+sub gnutriplet_to_debtriplet($)
+{
+    my ($gnu) = @_;
+    return unless defined($gnu);
+    my ($gnu_cpu, $gnu_os) = split(/-/, $gnu, 2);
+    return unless defined($gnu_cpu) && defined($gnu_os);
+
+    read_cputable();
+    read_ostable();
+
+    my ($os, $cpu);
+
+    foreach my $_cpu (@cpu) {
+	if ($gnu_cpu =~ /^$cputable_re{$_cpu}$/) {
+	    $cpu = $_cpu;
+	    last;
+	}
+    }
+
+    foreach my $_os (@os) {
+	if ($gnu_os =~ /^(.*-)?$ostable_re{$_os}$/) {
+	    $os = $_os;
+	    last;
+	}
+    }
+
+    return if !defined($cpu) || !defined($os);
+    return (split(/-/, $os, 2), $cpu);
+}
+
+sub gnutriplet_to_multiarch($)
+{
+    my ($gnu) = @_;
+    my ($cpu, $cdr) = split(/-/, $gnu, 2);
+
+    if ($cpu =~ /^i[456]86$/) {
+	return "i386-$cdr";
+    } else {
+	return $gnu;
+    }
+}
+
+sub debarch_to_multiarch($)
+{
+    my ($arch) = @_;
+
+    return gnutriplet_to_multiarch(debarch_to_gnutriplet($arch));
+}
+
+sub debtriplet_to_debarch(@)
+{
+    my ($abi, $os, $cpu) = @_;
+
+    read_triplettable();
+
+    if (!defined($abi) || !defined($os) || !defined($cpu)) {
+	return;
+    } elsif (exists $debtriplet_to_debarch{"$abi-$os-$cpu"}) {
+	return $debtriplet_to_debarch{"$abi-$os-$cpu"};
+    } else {
+	return;
+    }
+}
+
+sub debarch_to_debtriplet($)
+{
+    local ($_) = @_;
+    my $arch;
+
+    read_triplettable();
+
+    if (/^linux-([^-]*)/) {
+	# XXX: Might disappear in the future, not sure yet.
+	$arch = $1;
+    } else {
+	$arch = $_;
+    }
+
+    my $triplet = $debarch_to_debtriplet{$arch};
+
+    if (defined($triplet)) {
+	return split(/-/, $triplet, 3);
+    } else {
+	return;
+    }
+}
+
+sub debarch_to_gnutriplet($)
+{
+    my ($arch) = @_;
+
+    return debtriplet_to_gnutriplet(debarch_to_debtriplet($arch));
+}
+
+sub gnutriplet_to_debarch($)
+{
+    my ($gnu) = @_;
+
+    return debtriplet_to_debarch(gnutriplet_to_debtriplet($gnu));
+}
+
+sub debwildcard_to_debtriplet($)
+{
+    my ($arch) = @_;
+    my @tuple = split /-/, $arch, 3;
+
+    if (any { $_ eq 'any' } @tuple) {
+	if (scalar @tuple == 3) {
+	    return @tuple;
+	} elsif (scalar @tuple == 2) {
+	    return ('any', @tuple);
+	} else {
+	    return ('any', 'any', 'any');
+	}
+    } else {
+	return debarch_to_debtriplet($arch);
+    }
+}
+
+sub debarch_to_cpuattrs($)
+{
+    my ($arch) = @_;
+    my ($abi, $os, $cpu) = debarch_to_debtriplet($arch);
+
+    if (defined($cpu)) {
+        abitable_load();
+
+        return ($abibits{$abi} || $cpubits{$cpu}, $cpuendian{$cpu});
+    } else {
+        return;
+    }
+}
+
+sub debarch_eq($$)
+{
+    my ($a, $b) = @_;
+
+    return 1 if ($a eq $b);
+
+    my @a = debarch_to_debtriplet($a);
+    my @b = debarch_to_debtriplet($b);
+
+    return 0 if scalar @a != 3 or scalar @b != 3;
+
+    return ($a[0] eq $b[0] && $a[1] eq $b[1] && $a[2] eq $b[2]);
+}
+
+sub debarch_is($$)
+{
+    my ($real, $alias) = @_;
+
+    return 1 if ($alias eq $real or $alias eq 'any');
+
+    my @real = debarch_to_debtriplet($real);
+    my @alias = debwildcard_to_debtriplet($alias);
+
+    return 0 if scalar @real != 3 or scalar @alias != 3;
+
+    if (($alias[0] eq $real[0] || $alias[0] eq 'any') &&
+        ($alias[1] eq $real[1] || $alias[1] eq 'any') &&
+        ($alias[2] eq $real[2] || $alias[2] eq 'any')) {
+	return 1;
+    }
+
+    return 0;
+}
+
+sub debarch_is_wildcard($)
+{
+    my ($arch) = @_;
+
+    return 0 if $arch eq 'all';
+
+    my @triplet = debwildcard_to_debtriplet($arch);
+
+    return 0 if scalar @triplet != 3;
+    return 1 if any { $_ eq 'any' } @triplet;
+    return 0;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/BuildEnv.pm b/third_party/dpkg-dev/scripts/Dpkg/BuildEnv.pm
new file mode 100644
index 0000000..2615d5d
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/BuildEnv.pm
@@ -0,0 +1,107 @@
+# Copyright © 2012 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::BuildEnv;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+my %env_modified = ();
+my %env_accessed = ();
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildEnv - track build environment
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildEnv module is used by dpkg-buildflags to track the build
+environment variables being used and modified.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item $bf->set($varname, $value)
+
+Update the build environment variable $varname with value $value. Record
+it as being accessed and modified.
+
+=cut
+
+sub set {
+    my ($varname, $value) = @_;
+    $env_modified{$varname} = 1;
+    $env_accessed{$varname} = 1;
+    $ENV{$varname} = $value;
+}
+
+=item $bf->get($varname)
+
+Get the build environment variable $varname value. Record it as being
+accessed.
+
+=cut
+
+sub get {
+    my ($varname) = @_;
+    $env_accessed{$varname} = 1;
+    return $ENV{$varname};
+}
+
+=item $bf->has($varname)
+
+Return a boolean indicating whether the environment variable exists.
+Record it as being accessed.
+
+=cut
+
+sub has {
+    my ($varname) = @_;
+    $env_accessed{$varname} = 1;
+    return exists $ENV{$varname};
+}
+
+=item my @list = $bf->list_accessed()
+
+Returns a list of all environment variables that have been accessed.
+
+=cut
+
+sub list_accessed {
+    my @list = sort keys %env_accessed;
+    return @list;
+}
+
+=item my @list = $bf->list_modified()
+
+Returns a list of all environment variables that have been modified.
+
+=cut
+
+sub list_modified {
+    my @list = sort keys %env_modified;
+    return @list;
+}
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/BuildFlags.pm b/third_party/dpkg-dev/scripts/Dpkg/BuildFlags.pm
new file mode 100644
index 0000000..162e803
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/BuildFlags.pm
@@ -0,0 +1,456 @@
+# Copyright © 2010-2011 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::BuildFlags;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.03';
+
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::BuildEnv;
+use Dpkg::BuildOptions;
+use Dpkg::ErrorHandling;
+use Dpkg::Vendor qw(run_vendor_hook);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildFlags - query build flags
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildFlags object is used by dpkg-buildflags and can be used
+to query the same information.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $bf = Dpkg::BuildFlags->new()
+
+Create a new Dpkg::BuildFlags object. It will be initialized based
+on the value of several configuration files and environment variables.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+    };
+    bless $self, $class;
+    $self->load_vendor_defaults();
+    return $self;
+}
+
+=item $bf->load_vendor_defaults()
+
+Reset the flags stored to the default set provided by the vendor.
+
+=cut
+
+sub load_vendor_defaults {
+    my ($self) = @_;
+    $self->{options} = {};
+    $self->{source} = {};
+    $self->{features} = {};
+    my $build_opts = Dpkg::BuildOptions->new();
+    $self->{build_options} = $build_opts;
+    my $default_flags = $build_opts->has('noopt') ? '-g -O0' : '-g -O2';
+    $self->{flags} = {
+	CPPFLAGS => '',
+	CFLAGS   => $default_flags,
+	CXXFLAGS => $default_flags,
+	GCJFLAGS => $default_flags,
+	FFLAGS   => $default_flags,
+	LDFLAGS  => '',
+    };
+    $self->{origin} = {
+	CPPFLAGS => 'vendor',
+	CFLAGS   => 'vendor',
+	CXXFLAGS => 'vendor',
+	GCJFLAGS => 'vendor',
+	FFLAGS   => 'vendor',
+	LDFLAGS  => 'vendor',
+    };
+    $self->{maintainer} = {
+	CPPFLAGS => 0,
+	CFLAGS   => 0,
+	CXXFLAGS => 0,
+	GCJFLAGS => 0,
+	FFLAGS   => 0,
+	LDFLAGS  => 0,
+    };
+    # The Debian vendor hook will add hardening build flags
+    run_vendor_hook('update-buildflags', $self);
+}
+
+=item $bf->load_system_config()
+
+Update flags from the system configuration.
+
+=cut
+
+sub load_system_config {
+    my ($self) = @_;
+    $self->update_from_conffile("$Dpkg::CONFDIR/buildflags.conf", 'system');
+}
+
+=item $bf->load_user_config()
+
+Update flags from the user configuration.
+
+=cut
+
+sub load_user_config {
+    my ($self) = @_;
+    my $confdir = $ENV{XDG_CONFIG_HOME};
+    $confdir ||= $ENV{HOME} . '/.config' if defined $ENV{HOME};
+    if (defined $confdir) {
+        $self->update_from_conffile("$confdir/dpkg/buildflags.conf", 'user');
+    }
+}
+
+=item $bf->load_environment_config()
+
+Update flags based on user directives stored in the environment. See
+dpkg-buildflags(1) for details.
+
+=cut
+
+sub load_environment_config {
+    my ($self) = @_;
+    foreach my $flag (keys %{$self->{flags}}) {
+	my $envvar = 'DEB_' . $flag . '_SET';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->set($flag, Dpkg::BuildEnv::get($envvar), 'env');
+	}
+	$envvar = 'DEB_' . $flag . '_STRIP';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->strip($flag, Dpkg::BuildEnv::get($envvar), 'env');
+	}
+	$envvar = 'DEB_' . $flag . '_APPEND';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->append($flag, Dpkg::BuildEnv::get($envvar), 'env');
+	}
+	$envvar = 'DEB_' . $flag . '_PREPEND';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->prepend($flag, Dpkg::BuildEnv::get($envvar), 'env');
+	}
+    }
+}
+
+=item $bf->load_maintainer_config()
+
+Update flags based on maintainer directives stored in the environment. See
+dpkg-buildflags(1) for details.
+
+=cut
+
+sub load_maintainer_config {
+    my ($self) = @_;
+    foreach my $flag (keys %{$self->{flags}}) {
+	my $envvar = 'DEB_' . $flag . '_MAINT_SET';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->set($flag, Dpkg::BuildEnv::get($envvar), undef, 1);
+	}
+	$envvar = 'DEB_' . $flag . '_MAINT_STRIP';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->strip($flag, Dpkg::BuildEnv::get($envvar), undef, 1);
+	}
+	$envvar = 'DEB_' . $flag . '_MAINT_APPEND';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->append($flag, Dpkg::BuildEnv::get($envvar), undef, 1);
+	}
+	$envvar = 'DEB_' . $flag . '_MAINT_PREPEND';
+	if (Dpkg::BuildEnv::has($envvar)) {
+	    $self->prepend($flag, Dpkg::BuildEnv::get($envvar), undef, 1);
+	}
+    }
+}
+
+
+=item $bf->load_config()
+
+Call successively load_system_config(), load_user_config(),
+load_environment_config() and load_maintainer_config() to update the
+default build flags defined by the vendor.
+
+=cut
+
+sub load_config {
+    my ($self) = @_;
+    $self->load_system_config();
+    $self->load_user_config();
+    $self->load_environment_config();
+    $self->load_maintainer_config();
+}
+
+=item $bf->set($flag, $value, $source, $maint)
+
+Update the build flag $flag with value $value and record its origin as
+$source (if defined). Record it as maintainer modified if $maint is
+defined and true.
+
+=cut
+
+sub set {
+    my ($self, $flag, $value, $src, $maint) = @_;
+    $self->{flags}->{$flag} = $value;
+    $self->{origin}->{$flag} = $src if defined $src;
+    $self->{maintainer}->{$flag} = $maint if $maint;
+}
+
+=item $bf->set_feature($area, $feature, $enabled)
+
+Update the boolean state of whether a specific feature within a known
+feature area has been enabled. The only currently known feature area is
+"hardening".
+
+=cut
+
+sub set_feature {
+    my ($self, $area, $feature, $enabled) = @_;
+    $self->{features}{$area}{$feature} = $enabled;
+}
+
+=item $bf->strip($flag, $value, $source, $maint)
+
+Update the build flag $flag by stripping the flags listed in $value and
+record its origin as $source (if defined). Record it as maintainer modified
+if $maint is defined and true.
+
+=cut
+
+sub strip {
+    my ($self, $flag, $value, $src, $maint) = @_;
+    foreach my $tostrip (split(/\s+/, $value)) {
+	next unless length $tostrip;
+	$self->{flags}->{$flag} =~ s/(^|\s+)\Q$tostrip\E(\s+|$)/ /g;
+    }
+    $self->{flags}->{$flag} =~ s/^\s+//g;
+    $self->{flags}->{$flag} =~ s/\s+$//g;
+    $self->{origin}->{$flag} = $src if defined $src;
+    $self->{maintainer}->{$flag} = $maint if $maint;
+}
+
+=item $bf->append($flag, $value, $source, $maint)
+
+Append the options listed in $value to the current value of the flag $flag.
+Record its origin as $source (if defined). Record it as maintainer modified
+if $maint is defined and true.
+
+=cut
+
+sub append {
+    my ($self, $flag, $value, $src, $maint) = @_;
+    if (length($self->{flags}->{$flag})) {
+        $self->{flags}->{$flag} .= " $value";
+    } else {
+        $self->{flags}->{$flag} = $value;
+    }
+    $self->{origin}->{$flag} = $src if defined $src;
+    $self->{maintainer}->{$flag} = $maint if $maint;
+}
+
+=item $bf->prepend($flag, $value, $source, $maint)
+
+Prepend the options listed in $value to the current value of the flag $flag.
+Record its origin as $source (if defined). Record it as maintainer modified
+if $maint is defined and true.
+
+=cut
+
+sub prepend {
+    my ($self, $flag, $value, $src, $maint) = @_;
+    if (length($self->{flags}->{$flag})) {
+        $self->{flags}->{$flag} = "$value " . $self->{flags}->{$flag};
+    } else {
+        $self->{flags}->{$flag} = $value;
+    }
+    $self->{origin}->{$flag} = $src if defined $src;
+    $self->{maintainer}->{$flag} = $maint if $maint;
+}
+
+
+=item $bf->update_from_conffile($file, $source)
+
+Update the current build flags based on the configuration directives
+contained in $file. See dpkg-buildflags(1) for the format of the directives.
+
+$source is the origin recorded for any build flag set or modified.
+
+=cut
+
+sub update_from_conffile {
+    my ($self, $file, $src) = @_;
+    return unless -e $file;
+    open(my $conf_fh, '<', $file) or syserr(_g('cannot read %s'), $file);
+    while (<$conf_fh>) {
+        chomp;
+        next if /^\s*#/; # Skip comments
+        next if /^\s*$/; # Skip empty lines
+        if (/^(append|prepend|set|strip)\s+(\S+)\s+(\S.*\S)\s*$/i) {
+            my ($op, $flag, $value) = ($1, $2, $3);
+            unless (exists $self->{flags}->{$flag}) {
+                warning(_g('line %d of %s mentions unknown flag %s'), $., $file, $flag);
+                $self->{flags}->{$flag} = '';
+            }
+            if (lc($op) eq 'set') {
+                $self->set($flag, $value, $src);
+            } elsif (lc($op) eq 'strip') {
+                $self->strip($flag, $value, $src);
+            } elsif (lc($op) eq 'append') {
+                $self->append($flag, $value, $src);
+            } elsif (lc($op) eq 'prepend') {
+                $self->prepend($flag, $value, $src);
+            }
+        } else {
+            warning(_g('line %d of %s is invalid, it has been ignored'), $., $file);
+        }
+    }
+    close($conf_fh);
+}
+
+=item $bf->get($flag)
+
+Return the value associated to the flag. It might be undef if the
+flag doesn't exist.
+
+=cut
+
+sub get {
+    my ($self, $key) = @_;
+    return $self->{flags}{$key};
+}
+
+=item $bf->get_feature_areas()
+
+Return the feature areas (i.e. the area values has_features will return
+true for).
+
+=cut
+
+sub get_feature_areas {
+    my ($self) = @_;
+    return keys %{$self->{features}};
+}
+
+=item $bf->get_features($area)
+
+Return, for the given area, a hash with keys as feature names, and values
+as booleans indicating whether the feature is enabled or not.
+
+=cut
+
+sub get_features {
+    my ($self, $area) = @_;
+    return %{$self->{features}{$area}};
+}
+
+=item $bf->get_origin($flag)
+
+Return the origin associated to the flag. It might be undef if the
+flag doesn't exist.
+
+=cut
+
+sub get_origin {
+    my ($self, $key) = @_;
+    return $self->{origin}{$key};
+}
+
+=item $bf->is_maintainer_modified($flag)
+
+Return true if the flag is modified by the maintainer.
+
+=cut
+
+sub is_maintainer_modified {
+    my ($self, $key) = @_;
+    return $self->{maintainer}{$key};
+}
+
+=item $bf->has_features($area)
+
+Returns true if the given area of features is known, and false otherwise.
+The only currently recognized area is "hardening".
+
+=cut
+
+sub has_features {
+    my ($self, $area) = @_;
+    return exists $self->{features}{$area};
+}
+
+=item $bf->has($option)
+
+Returns a boolean indicating whether the flags exists in the object.
+
+=cut
+
+sub has {
+    my ($self, $key) = @_;
+    return exists $self->{flags}{$key};
+}
+
+=item my @flags = $bf->list()
+
+Returns the list of flags stored in the object.
+
+=cut
+
+sub list {
+    my ($self) = @_;
+    my @list = sort keys %{$self->{flags}};
+    return @list;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New method: $bf->prepend() very similar to append(). Implement support of
+the prepend operation everywhere.
+
+New method: $bf->load_maintainer_config() that update the build flags
+based on the package maintainer directives.
+
+=head2 Version 1.02
+
+New methods: $bf->get_features(), $bf->has_features(), $bf->set_feature().
+
+=head2 Version 1.03
+
+New method: $bf->get_feature_areas() to list possible values for
+$bf->get_features.
+
+New method $bf->is_maintainer_modified() and new optional parameter to
+$bf->set(), $bf->append(), $bf->prepend(), $bf->strip().
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/BuildOptions.pm b/third_party/dpkg-dev/scripts/Dpkg/BuildOptions.pm
new file mode 100644
index 0000000..90213aa
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/BuildOptions.pm
@@ -0,0 +1,204 @@
+# Copyright © 2007 Frank Lichtenheld <djpig@debian.org>
+# Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::BuildOptions;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::BuildEnv;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildOptions - parse and update build options
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildOptions object can be used to manipulate options stored
+in environment variables like DEB_BUILD_OPTIONS and
+DEB_BUILD_MAINT_OPTIONS.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $bo = Dpkg::BuildOptions->new(%opts)
+
+Create a new Dpkg::BuildOptions object. It will be initialized based
+on the value of the environment variable named $opts{envvar} (or
+DEB_BUILD_OPTIONS if that option is not set).
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+        options => {},
+	source => {},
+	envvar => $opts{envvar} // 'DEB_BUILD_OPTIONS',
+    };
+    bless $self, $class;
+    $self->merge(Dpkg::BuildEnv::get($self->{envvar}), $self->{envvar});
+    return $self;
+}
+
+=item $bo->reset()
+
+Reset the object to not have any option (it's empty).
+
+=cut
+
+sub reset {
+    my ($self) = @_;
+    $self->{options} = {};
+    $self->{source} = {};
+}
+
+=item $bo->merge($content, $source)
+
+Merge the options set in $content and record that they come from the
+source $source. $source is mainly used in warning messages currently
+to indicate where invalid options have been detected.
+
+$content is a space separated list of options with optional assigned
+values like "nocheck parallel=2".
+
+=cut
+
+sub merge {
+    my ($self, $content, $source) = @_;
+    return 0 unless defined $content;
+    my $count = 0;
+    foreach (split(/\s+/, $content)) {
+	unless (/^([a-z][a-z0-9_-]*)(?:=(\S*))?$/) {
+            warning(_g('invalid flag in %s: %s'), $source, $_);
+            next;
+        }
+	$count += $self->set($1, $2, $source);
+    }
+    return $count;
+}
+
+=item $bo->set($option, $value, [$source])
+
+Store the given option in the objet with the given value. It's legitimate
+for a value to be undefined if the option is a simple boolean (its
+presence means true, its absence means false). The $source is optional
+and indicates where the option comes from.
+
+The known options have their values checked for sanity. Options without
+values have their value removed and options with invalid values are
+discarded.
+
+=cut
+
+sub set {
+    my ($self, $key, $value, $source) = @_;
+
+    # Sanity checks
+    if ($key =~ /^(noopt|nostrip|nocheck)$/ && defined($value)) {
+	$value = undef;
+    } elsif ($key eq 'parallel')  {
+	$value //= '';
+	return 0 if $value !~ /^\d*$/;
+    }
+
+    $self->{options}{$key} = $value;
+    $self->{source}{$key} = $source;
+
+    return 1;
+}
+
+=item $bo->get($option)
+
+Return the value associated to the option. It might be undef even if the
+option exists. You might want to check with $bo->has($option) to verify if
+the option is stored in the object.
+
+=cut
+
+sub get {
+    my ($self, $key) = @_;
+    return $self->{options}{$key};
+}
+
+=item $bo->has($option)
+
+Returns a boolean indicating whether the option is stored in the object.
+
+=cut
+
+sub has {
+    my ($self, $key) = @_;
+    return exists $self->{options}{$key};
+}
+
+=item $string = $bo->output($fh)
+
+Return a string representation of the build options suitable to be
+assigned to an environment variable. Can optionnaly output that string to
+the given filehandle.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $o = $self->{options};
+    my $res = join(' ', map { defined($o->{$_}) ? $_ . '=' . $o->{$_} : $_ } sort keys %$o);
+    print { $fh } $res if defined $fh;
+    return $res;
+}
+
+=item $bo->export([$var])
+
+Export the build options to the given environment variable. If omitted,
+the environment variable defined at creation time is assumed. The value
+set to the variable is also returned.
+
+=cut
+
+sub export {
+    my ($self, $var) = @_;
+    $var = $self->{envvar} unless defined $var;
+    my $content = $self->output();
+    Dpkg::BuildEnv::set($var, $content);
+    return $content;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+Enable to use another environment variable instead of DEB_BUILD_OPTIONS.
+Thus add support for the "envvar" option at creation time.
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/BuildProfiles.pm b/third_party/dpkg-dev/scripts/Dpkg/BuildProfiles.pm
new file mode 100644
index 0000000..98c86d36
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/BuildProfiles.pm
@@ -0,0 +1,82 @@
+# Copyright © 2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::BuildProfiles;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+our @EXPORT_OK = qw(get_build_profiles set_build_profiles);
+
+use Exporter qw(import);
+
+use Dpkg::BuildEnv;
+
+my $cache_profiles;
+my @build_profiles;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildProfiles - handle build profiles
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildProfiles module provides functions to handle the build
+profiles.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my @profiles = get_build_profiles()
+
+Get an array with the currently active build profiles, taken from
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub get_build_profiles {
+    return @build_profiles if $cache_profiles;
+
+    if (Dpkg::BuildEnv::has('DEB_BUILD_PROFILES')) {
+        @build_profiles = split / /, Dpkg::BuildEnv::get('DEB_BUILD_PROFILES');
+    }
+    $cache_profiles = 1;
+
+    return @build_profiles;
+}
+
+=item set_build_profiles(@profiles)
+
+Set C<@profiles> as the current active build profiles, by setting
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub set_build_profiles {
+    my (@profiles) = @_;
+
+    @build_profiles = @profiles;
+    Dpkg::BuildEnv::set('DEB_BUILD_PROFILES', join ' ', @profiles);
+}
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Changelog.pm b/third_party/dpkg-dev/scripts/Dpkg/Changelog.pm
new file mode 100644
index 0000000..40e0628e
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Changelog.pm
@@ -0,0 +1,704 @@
+# Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
+# Copyright © 2009       Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Changelog - base class to implement a changelog parser
+
+=head1 DESCRIPTION
+
+Dpkg::Changelog is a class representing a changelog file
+as an array of changelog entries (Dpkg::Changelog::Entry).
+By deriving this object and implementing its parse method, you
+add the ability to fill this object with changelog entries.
+
+=head2 FUNCTIONS
+
+=cut
+
+package Dpkg::Changelog;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling qw(:DEFAULT report);
+use Dpkg::Control;
+use Dpkg::Control::Changelog;
+use Dpkg::Control::Fields;
+use Dpkg::Index;
+use Dpkg::Version;
+use Dpkg::Vendor qw(run_vendor_hook);
+
+use parent qw(Dpkg::Interface::Storable);
+
+use overload
+    '@{}' => sub { return $_[0]->{data} };
+
+=over 4
+
+=item my $c = Dpkg::Changelog->new(%options)
+
+Creates a new changelog object.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+    my $self = {
+	verbose => 1,
+	parse_errors => []
+    };
+    bless $self, $class;
+    $self->set_options(%opts);
+    return $self;
+}
+
+=item $c->load($filename)
+
+Parse $filename as a changelog.
+
+=cut
+
+=item $c->set_options(%opts)
+
+Change the value of some options. "verbose" (defaults to 1) defines
+whether parse errors are displayed as warnings by default. "reportfile"
+is a string to use instead of the name of the file parsed, in particular
+in error messages. "range" defines the range of entries that we want to
+parse, the parser will stop as soon as it has parsed enough data to
+satisfy $c->get_range($opts{range}).
+
+=cut
+
+sub set_options {
+    my ($self, %opts) = @_;
+    $self->{$_} = $opts{$_} foreach keys %opts;
+}
+
+=item $c->reset_parse_errors()
+
+Can be used to delete all information about errors occurred during
+previous L<parse> runs.
+
+=cut
+
+sub reset_parse_errors {
+    my ($self) = @_;
+    $self->{parse_errors} = [];
+}
+
+=item $c->parse_error($file, $line_nr, $error, [$line])
+
+Record a new parse error in $file at line $line_nr. The error message is
+specified with $error and a copy of the line can be recorded in $line.
+
+=cut
+
+sub parse_error {
+    my ($self, $file, $line_nr, $error, $line) = @_;
+    shift;
+
+    push @{$self->{parse_errors}}, [ @_ ];
+
+    if ($self->{verbose}) {
+	if ($line) {
+	    warning("%20s(l$line_nr): $error\nLINE: $line", $file);
+	} else {
+	    warning("%20s(l$line_nr): $error", $file);
+	}
+    }
+}
+
+=item $c->get_parse_errors()
+
+Returns all error messages from the last L<parse> run.
+If called in scalar context returns a human readable
+string representation. If called in list context returns
+an array of arrays. Each of these arrays contains
+
+=over 4
+
+=item 1.
+
+a string describing the origin of the data (a filename usually). If the
+reportfile configuration option was given, its value will be used instead.
+
+=item 2.
+
+the line number where the error occurred
+
+=item 3.
+
+an error description
+
+=item 4.
+
+the original line
+
+=back
+
+=cut
+
+sub get_parse_errors {
+    my ($self) = @_;
+
+    if (wantarray) {
+	return @{$self->{parse_errors}};
+    } else {
+	my $res = '';
+	foreach my $e (@{$self->{parse_errors}}) {
+	    if ($e->[3]) {
+		$res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e );
+	    } else {
+		$res .= report(_g('warning'), _g('%s(l%s): %s'), @$e);
+	    }
+	}
+	return $res;
+    }
+}
+
+=item $c->set_unparsed_tail($tail)
+
+Add a string representing unparsed lines after the changelog entries.
+Use undef as $tail to remove the unparsed lines currently set.
+
+=item $c->get_unparsed_tail()
+
+Return a string representing the unparsed lines after the changelog
+entries. Returns undef if there's no such thing.
+
+=cut
+
+sub set_unparsed_tail {
+    my ($self, $tail) = @_;
+    $self->{unparsed_tail} = $tail;
+}
+
+sub get_unparsed_tail {
+    my ($self) = @_;
+    return $self->{unparsed_tail};
+}
+
+=item @{$c}
+
+Returns all the Dpkg::Changelog::Entry objects contained in this changelog
+in the order in which they have been parsed.
+
+=item $c->get_range($range)
+
+Returns an array (if called in list context) or a reference to an array of
+Dpkg::Changelog::Entry objects which each represent one entry of the
+changelog. $range is a hash reference describing the range of entries
+to return. See section L<"RANGE SELECTION">.
+
+=cut
+
+sub __sanity_check_range {
+    my ($self, $r) = @_;
+    my $data = $self->{data};
+
+    if (defined($r->{offset}) and not defined($r->{count})) {
+	warning(_g("'offset' without 'count' has no effect")) if $self->{verbose};
+	delete $r->{offset};
+    }
+
+    ## no critic (ControlStructures::ProhibitUntilBlocks)
+    if ((defined($r->{count}) || defined($r->{offset})) &&
+        (defined($r->{from}) || defined($r->{since}) ||
+	 defined($r->{to}) || defined($r->{until})))
+    {
+	warning(_g("you can't combine 'count' or 'offset' with any other " .
+		   'range option')) if $self->{verbose};
+	delete $r->{from};
+	delete $r->{since};
+	delete $r->{to};
+	delete $r->{until};
+    }
+    if (defined($r->{from}) && defined($r->{since})) {
+	warning(_g("you can only specify one of 'from' and 'since', using " .
+		   "'since'")) if $self->{verbose};
+	delete $r->{from};
+    }
+    if (defined($r->{to}) && defined($r->{until})) {
+	warning(_g("you can only specify one of 'to' and 'until', using " .
+		   "'until'")) if $self->{verbose};
+	delete $r->{to};
+    }
+
+    # Handle non-existing versions
+    my (%versions, @versions);
+    foreach my $entry (@{$data}) {
+        $versions{$entry->get_version()->as_string()} = 1;
+        push @versions, $entry->get_version()->as_string();
+    }
+    if ((defined($r->{since}) and not exists $versions{$r->{since}})) {
+        warning(_g("'%s' option specifies non-existing version"), 'since');
+        warning(_g('use newest entry that is earlier than the one specified'));
+        foreach my $v (@versions) {
+            if (version_compare_relation($v, REL_LT, $r->{since})) {
+                $r->{since} = $v;
+                last;
+            }
+        }
+        if (not exists $versions{$r->{since}}) {
+            # No version was earlier, include all
+            warning(_g('none found, starting from the oldest entry'));
+            delete $r->{since};
+            $r->{from} = $versions[-1];
+        }
+    }
+    if ((defined($r->{from}) and not exists $versions{$r->{from}})) {
+        warning(_g("'%s' option specifies non-existing version"), 'from');
+        warning(_g('use oldest entry that is later than the one specified'));
+        my $oldest;
+        foreach my $v (@versions) {
+            if (version_compare_relation($v, REL_GT, $r->{from})) {
+                $oldest = $v;
+            }
+        }
+        if (defined($oldest)) {
+            $r->{from} = $oldest;
+        } else {
+            warning(_g("no such entry found, ignoring '%s' parameter"), 'from');
+            delete $r->{from}; # No version was oldest
+        }
+    }
+    if (defined($r->{until}) and not exists $versions{$r->{until}}) {
+        warning(_g("'%s' option specifies non-existing version"), 'until');
+        warning(_g('use oldest entry that is later than the one specified'));
+        my $oldest;
+        foreach my $v (@versions) {
+            if (version_compare_relation($v, REL_GT, $r->{until})) {
+                $oldest = $v;
+            }
+        }
+        if (defined($oldest)) {
+            $r->{until} = $oldest;
+        } else {
+            warning(_g("no such entry found, ignoring '%s' parameter"), 'until');
+            delete $r->{until}; # No version was oldest
+        }
+    }
+    if (defined($r->{to}) and not exists $versions{$r->{to}}) {
+        warning(_g("'%s' option specifies non-existing version"), 'to');
+        warning(_g('use newest entry that is earlier than the one specified'));
+        foreach my $v (@versions) {
+            if (version_compare_relation($v, REL_LT, $r->{to})) {
+                $r->{to} = $v;
+                last;
+            }
+        }
+        if (not exists $versions{$r->{to}}) {
+            # No version was earlier
+            warning(_g("no such entry found, ignoring '%s' parameter"), 'to');
+            delete $r->{to};
+        }
+    }
+
+    if (defined($r->{since}) and $data->[0]->get_version() eq $r->{since}) {
+	warning(_g("'since' option specifies most recent version, ignoring"));
+	delete $r->{since};
+    }
+    if (defined($r->{until}) and $data->[-1]->get_version() eq $r->{until}) {
+	warning(_g("'until' option specifies oldest version, ignoring"));
+	delete $r->{until};
+    }
+    ## use critic
+}
+
+sub get_range {
+    my ($self, $range) = @_;
+    $range //= {};
+    my $res = $self->_data_range($range);
+    if (defined $res) {
+	return @$res if wantarray;
+	return $res;
+    } else {
+	return;
+    }
+}
+
+sub _is_full_range {
+    my ($self, $range) = @_;
+
+    return 1 if $range->{all};
+
+    # If no range delimiter is specified, we want everything.
+    foreach (qw(since until from to count offset)) {
+        return 0 if exists $range->{$_};
+    }
+
+    return 1;
+}
+
+sub _data_range {
+    my ($self, $range) = @_;
+
+    my $data = $self->{data} or return;
+
+    return [ @$data ] if $self->_is_full_range($range);
+
+    $self->__sanity_check_range($range);
+
+    my ($start, $end);
+    if (defined($range->{count})) {
+	my $offset = $range->{offset} || 0;
+	my $count = $range->{count};
+	# Convert count/offset in start/end
+	if ($offset > 0) {
+	    $offset -= ($count < 0);
+	} elsif ($offset < 0) {
+	    $offset = $#$data + ($count > 0) + $offset;
+	} else {
+	    $offset = $#$data if $count < 0;
+	}
+	$start = $end = $offset;
+	$start += $count+1 if $count < 0;
+	$end += $count-1 if $count > 0;
+	# Check limits
+	$start = 0 if $start < 0;
+	return if $start > $#$data;
+	$end = $#$data if $end > $#$data;
+	return if $end < 0;
+	$end = $start if $end < $start;
+	return [ @{$data}[$start .. $end] ];
+    }
+
+    ## no critic (ControlStructures::ProhibitUntilBlocks)
+    my @result;
+    my $include = 1;
+    $include = 0 if defined($range->{to}) or defined($range->{until});
+    foreach (@$data) {
+	my $v = $_->get_version();
+	$include = 1 if defined($range->{to}) and $v eq $range->{to};
+	last if defined($range->{since}) and $v eq $range->{since};
+
+	push @result, $_ if $include;
+
+	$include = 1 if defined($range->{until}) and $v eq $range->{until};
+	last if defined($range->{from}) and $v eq $range->{from};
+    }
+    ## use critic
+
+    return \@result if scalar(@result);
+    return;
+}
+
+=item $c->abort_early()
+
+Returns true if enough data have been parsed to be able to return all
+entries selected by the range set at creation (or with set_options).
+
+=cut
+
+sub abort_early {
+    my ($self) = @_;
+
+    my $data = $self->{data} or return;
+    my $r = $self->{range} or return;
+    my $count = $r->{count} || 0;
+    my $offset = $r->{offset} || 0;
+
+    return if $self->_is_full_range($r);
+    return if $offset < 0 or $count < 0;
+    if (defined($r->{count})) {
+	if ($offset > 0) {
+	    $offset -= ($count < 0);
+	}
+	my $start = my $end = $offset;
+	$end += $count-1 if $count > 0;
+	return ($start < @$data and $end < @$data);
+    }
+
+    return unless defined($r->{since}) or defined($r->{from});
+    foreach (@$data) {
+	my $v = $_->get_version();
+	return 1 if defined($r->{since}) and $v eq $r->{since};
+	return 1 if defined($r->{from}) and $v eq $r->{from};
+    }
+
+    return;
+}
+
+=item $c->save($filename)
+
+Save the changelog in the given file.
+
+=item $c->output()
+
+=item "$c"
+
+Returns a string representation of the changelog (it's a concatenation of
+the string representation of the individual changelog entries).
+
+=item $c->output($fh)
+
+Output the changelog to the given filehandle.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str = '';
+    foreach my $entry (@{$self}) {
+	my $text = $entry->output();
+	print { $fh } $text if defined $fh;
+	$str .= $text if defined wantarray;
+    }
+    my $text = $self->get_unparsed_tail();
+    if (defined $text) {
+	print { $fh } $text if defined $fh;
+	$str .= $text if defined wantarray;
+    }
+    return $str;
+}
+
+=item my $control = $c->dpkg($range)
+
+Returns a Dpkg::Control::Changelog object representing the entries selected
+by the optional range specifier (see L<"RANGE SELECTION"> for details).
+Returns undef in no entries are matched.
+
+The following fields are contained in the object:
+
+=over 4
+
+=item Source
+
+package name (in the first entry)
+
+=item Version
+
+packages' version (from first entry)
+
+=item Distribution
+
+target distribution (from first entry)
+
+=item Urgency
+
+urgency (highest of all printed entries)
+
+=item Maintainer
+
+person that created the (first) entry
+
+=item Date
+
+date of the (first) entry
+
+=item Closes
+
+bugs closed by the entry/entries, sorted by bug number
+
+=item Changes
+
+content of the the entry/entries
+
+=back
+
+=cut
+
+our ( @URGENCIES, %URGENCIES );
+BEGIN {
+    @URGENCIES = qw(low medium high critical emergency);
+    my $i = 1;
+    %URGENCIES = map { $_ => $i++ } @URGENCIES;
+}
+
+sub dpkg {
+    my ($self, $range) = @_;
+
+    my @data = $self->get_range($range) or return;
+    my $src = shift @data;
+
+    my $f = Dpkg::Control::Changelog->new();
+    $f->{Urgency} = $src->get_urgency() || 'unknown';
+    $f->{Source} = $src->get_source() || 'unknown';
+    $f->{Version} = $src->get_version() // 'unknown';
+    $f->{Distribution} = join(' ', $src->get_distributions());
+    $f->{Maintainer} = $src->get_maintainer() || '';
+    $f->{Date} = $src->get_timestamp() || '';
+    $f->{Changes} = $src->get_dpkg_changes();
+
+    # handle optional fields
+    my $opts = $src->get_optional_fields();
+    my %closes;
+    foreach (keys %$opts) {
+	if (/^Urgency$/i) { # Already dealt
+	} elsif (/^Closes$/i) {
+	    $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes}));
+	} else {
+	    field_transfer_single($opts, $f);
+	}
+    }
+
+    foreach my $bin (@data) {
+	my $oldurg = $f->{Urgency} || '';
+	my $oldurgn = $URGENCIES{$f->{Urgency}} || -1;
+	my $newurg = $bin->get_urgency() || '';
+	my $newurgn = $URGENCIES{$newurg} || -1;
+	$f->{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg;
+	$f->{Changes} .= "\n" . $bin->get_dpkg_changes();
+
+	# handle optional fields
+	$opts = $bin->get_optional_fields();
+	foreach (keys %$opts) {
+	    if (/^Closes$/i) {
+		$closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes}));
+	    } elsif (not exists $f->{$_}) { # Don't overwrite an existing field
+		field_transfer_single($opts, $f);
+	    }
+	}
+    }
+
+    if (scalar keys %closes) {
+	$f->{Closes} = join ' ', sort { $a <=> $b } keys %closes;
+    }
+    run_vendor_hook('post-process-changelog-entry', $f);
+
+    return $f;
+}
+
+=item my @controls = $c->rfc822($range)
+
+Returns a Dpkg::Index containing Dpkg::Control::Changelog objects where
+each object represents one entry in the changelog that is part of the
+range requested (see L<"RANGE SELECTION"> for details). For the format of
+such an object see the description of the L<"dpkg"> method (while ignoring
+the remarks about which values are taken from the first entry).
+
+=cut
+
+sub rfc822 {
+    my ($self, $range) = @_;
+
+    my @data = $self->get_range($range) or return;
+    my $index = Dpkg::Index->new(type => CTRL_CHANGELOG);
+
+    foreach my $entry (@data) {
+	my $f = Dpkg::Control::Changelog->new();
+	$f->{Urgency} = $entry->get_urgency() || 'unknown';
+	$f->{Source} = $entry->get_source() || 'unknown';
+	$f->{Version} = $entry->get_version() // 'unknown';
+	$f->{Distribution} = join(' ', $entry->get_distributions());
+	$f->{Maintainer} = $entry->get_maintainer() || '';
+	$f->{Date} = $entry->get_timestamp() || '';
+	$f->{Changes} = $entry->get_dpkg_changes();
+
+	# handle optional fields
+	my $opts = $entry->get_optional_fields();
+	foreach (keys %$opts) {
+	    field_transfer_single($opts, $f) unless exists $f->{$_};
+	}
+
+        run_vendor_hook('post-process-changelog-entry', $f);
+
+	$index->add($f);
+    }
+    return $index;
+}
+
+=back
+
+=head1 RANGE SELECTION
+
+A range selection is described by a hash reference where
+the allowed keys and values are described below.
+
+The following options take a version number as value.
+
+=over 4
+
+=item since
+
+Causes changelog information from all versions strictly
+later than B<version> to be used.
+
+=item until
+
+Causes changelog information from all versions strictly
+earlier than B<version> to be used.
+
+=item from
+
+Similar to C<since> but also includes the information for the
+specified B<version> itself.
+
+=item to
+
+Similar to C<until> but also includes the information for the
+specified B<version> itself.
+
+=back
+
+The following options don't take version numbers as values:
+
+=over 4
+
+=item all
+
+If set to a true value, all entries of the changelog are returned,
+this overrides all other options.
+
+=item count
+
+Expects a signed integer as value. Returns C<value> entries from the
+top of the changelog if set to a positive integer, and C<abs(value)>
+entries from the tail if set to a negative integer.
+
+=item offset
+
+Expects a signed integer as value. Changes the starting point for
+C<count>, either counted from the top (positive integer) or from
+the tail (negative integer). C<offset> has no effect if C<count>
+wasn't given as well.
+
+=back
+
+Some examples for the above options. Imagine an example changelog with
+entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
+
+            Range                           Included entries
+ C<{ since =E<gt> '2.0' }>                  3.1, 3.0, 2.2
+ C<{ until =E<gt> '2.0' }>                  1.3, 1.2
+ C<{ from =E<gt> '2.0' }>                   3.1, 3.0, 2.2, 2.1, 2.0
+ C<{ to =E<gt> '2.0' }>                     2.0, 1.3, 1.2
+ C<{ count =E<gt> 2 }>                      3.1, 3.0
+ C<{ count =E<gt> -2 }>	                    1.3, 1.2
+ C<{ count =E<gt> 3, offset=E<gt> 2 }>      2.2, 2.1, 2.0
+ C<{ count =E<gt> 2, offset=E<gt> -3 }>     2.0, 1.3
+ C<{ count =E<gt> -2, offset=E<gt> 3 }>     3.0, 2.2
+ C<{ count =E<gt> -2, offset=E<gt> -3 }>    2.2, 2.1
+
+Any combination of one option of C<since> and C<from> and one of
+C<until> and C<to> returns the intersection of the two results
+with only one of the options specified.
+
+=head1 AUTHOR
+
+Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt>
+Raphaël Hertzog, E<lt>hertzog@debian.orgE<gt>
+
+=cut
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Changelog/Debian.pm b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Debian.pm
new file mode 100644
index 0000000..1643fb3
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Debian.pm
@@ -0,0 +1,208 @@
+# Copyright © 1996 Ian Jackson
+# Copyright © 2005 Frank Lichtenheld <frank@lichtenheld.de>
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Changelog::Debian - parse Debian changelogs
+
+=head1 DESCRIPTION
+
+Dpkg::Changelog::Debian parses Debian changelogs as described in the Debian
+policy (version 3.6.2.1 at the time of this writing). See section
+L<"SEE ALSO"> for locations where to find this definition.
+
+The parser tries to ignore most cruft like # or /* */ style comments,
+CVS comments, vim variables, emacs local variables and stuff from
+older changelogs with other formats at the end of the file.
+NOTE: most of these are ignored silently currently, there is no
+parser error issued for them. This should become configurable in the
+future.
+
+=head2 METHODS
+
+=cut
+
+package Dpkg::Changelog::Debian;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Gettext;
+use Dpkg::File;
+use Dpkg::Changelog qw(:util);
+use parent qw(Dpkg::Changelog);
+use Dpkg::Changelog::Entry::Debian qw(match_header match_trailer);
+
+use constant {
+    FIRST_HEADING => _g('first heading'),
+    NEXT_OR_EOF => _g('next heading or eof'),
+    START_CHANGES => _g('start of change data'),
+    CHANGES_OR_TRAILER => _g('more change data or trailer'),
+};
+
+=over 4
+
+=item $c->parse($fh, $description)
+
+Read the filehandle and parse a Debian changelog in it. Returns the number
+of changelog entries that have been parsed with success.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $file) = @_;
+    $file = $self->{reportfile} if exists $self->{reportfile};
+
+    $self->reset_parse_errors;
+
+    $self->{data} = [];
+    $self->set_unparsed_tail(undef);
+
+    my $expect = FIRST_HEADING;
+    my $entry = Dpkg::Changelog::Entry::Debian->new();
+    my @blanklines = ();
+    my $unknowncounter = 1; # to make version unique, e.g. for using as id
+
+    while (<$fh>) {
+	chomp;
+	if (match_header($_)) {
+	    unless ($expect eq FIRST_HEADING || $expect eq NEXT_OR_EOF) {
+		$self->parse_error($file, $.,
+		    sprintf(_g('found start of entry where expected %s'),
+		    $expect), "$_");
+	    }
+	    unless ($entry->is_empty) {
+		push @{$self->{data}}, $entry;
+		$entry = Dpkg::Changelog::Entry::Debian->new();
+		last if $self->abort_early();
+	    }
+	    $entry->set_part('header', $_);
+	    foreach my $error ($entry->check_header()) {
+		$self->parse_error($file, $., $error, $_);
+	    }
+	    $expect= START_CHANGES;
+	    @blanklines = ();
+	} elsif (m/^(;;\s*)?Local variables:/io) {
+	    last; # skip Emacs variables at end of file
+	} elsif (m/^vim:/io) {
+	    last; # skip vim variables at end of file
+	} elsif (m/^\$\w+:.*\$/o) {
+	    next; # skip stuff that look like a CVS keyword
+	} elsif (m/^\# /o) {
+	    next; # skip comments, even that's not supported
+	} elsif (m{^/\*.*\*/}o) {
+	    next; # more comments
+	} elsif (m/^(\w+\s+\w+\s+\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}\s+[\w\s]*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)/o
+		 || m/^(\w+\s+\w+\s+\d{1,2},?\s*\d{4})\s+(.*)\s+(<|\()(.*)(\)|>)/o
+		 || m/^(\w[-+0-9a-z.]*) \(([^\(\) \t]+)\)\;?/io
+		 || m/^([\w.+-]+)(-| )(\S+) Debian (\S+)/io
+		 || m/^Changes from version (.*) to (.*):/io
+		 || m/^Changes for [\w.+-]+-[\w.+-]+:?\s*$/io
+		 || m/^Old Changelog:\s*$/io
+		 || m/^(?:\d+:)?\w[\w.+~-]*:?\s*$/o) {
+	    # save entries on old changelog format verbatim
+	    # we assume the rest of the file will be in old format once we
+	    # hit it for the first time
+	    $self->set_unparsed_tail("$_\n" . file_slurp($fh));
+	} elsif (m/^\S/) {
+	    $self->parse_error($file, $., _g('badly formatted heading line'), "$_");
+	} elsif (match_trailer($_)) {
+	    unless ($expect eq CHANGES_OR_TRAILER) {
+		$self->parse_error($file, $.,
+		    sprintf(_g('found trailer where expected %s'), $expect), "$_");
+	    }
+	    $entry->set_part('trailer', $_);
+	    $entry->extend_part('blank_after_changes', [ @blanklines ]);
+	    @blanklines = ();
+	    foreach my $error ($entry->check_trailer()) {
+		$self->parse_error($file, $., $error, $_);
+	    }
+	    $expect = NEXT_OR_EOF;
+	} elsif (m/^ \-\-/) {
+	    $self->parse_error($file, $., _g('badly formatted trailer line'), "$_");
+	} elsif (m/^\s{2,}(\S)/) {
+	    unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
+		$self->parse_error($file, $., sprintf(_g('found change data' .
+		    ' where expected %s'), $expect), "$_");
+		if ($expect eq NEXT_OR_EOF and not $entry->is_empty) {
+		    # lets assume we have missed the actual header line
+		    push @{$self->{data}}, $entry;
+		    $entry = Dpkg::Changelog::Entry::Debian->new();
+		    $entry->set_part('header', 'unknown (unknown' . ($unknowncounter++) . ') unknown; urgency=unknown');
+		}
+	    }
+	    # Keep raw changes
+	    $entry->extend_part('changes', [ @blanklines, $_ ]);
+	    @blanklines = ();
+	    $expect = CHANGES_OR_TRAILER;
+	} elsif (!m/\S/) {
+	    if ($expect eq START_CHANGES) {
+		$entry->extend_part('blank_after_header', $_);
+		next;
+	    } elsif ($expect eq NEXT_OR_EOF) {
+		$entry->extend_part('blank_after_trailer', $_);
+		next;
+	    } elsif ($expect ne CHANGES_OR_TRAILER) {
+		$self->parse_error($file, $.,
+		    sprintf(_g('found blank line where expected %s'), $expect));
+	    }
+	    push @blanklines, $_;
+	} else {
+	    $self->parse_error($file, $., _g('unrecognized line'), "$_");
+	    unless ($expect eq START_CHANGES or $expect eq CHANGES_OR_TRAILER) {
+		# lets assume change data if we expected it
+		$entry->extend_part('changes', [ @blanklines, $_]);
+		@blanklines = ();
+		$expect = CHANGES_OR_TRAILER;
+	    }
+	}
+    }
+
+    unless ($expect eq NEXT_OR_EOF) {
+	$self->parse_error($file, $., sprintf(_g('found eof where expected %s'),
+					      $expect));
+    }
+    unless ($entry->is_empty) {
+	push @{$self->{data}}, $entry;
+    }
+
+    return scalar @{$self->{data}};
+}
+
+1;
+__END__
+
+=back
+
+=head1 SEE ALSO
+
+Dpkg::Changelog
+
+Description of the Debian changelog format in the Debian policy:
+L<http://www.debian.org/doc/debian-policy/ch-source.html#s-dpkgchangelog>.
+
+=head1 AUTHORS
+
+Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt>
+Raphaël Hertzog, E<lt>hertzog@debian.orgE<gt>
+
+=cut
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry.pm b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry.pm
new file mode 100644
index 0000000..3277ab6
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry.pm
@@ -0,0 +1,306 @@
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Changelog::Entry;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Carp;
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Changelog;
+
+use overload
+    '""' => \&output,
+    'eq' => sub { defined($_[1]) and "$_[0]" eq "$_[1]" },
+    fallback => 1;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Changelog::Entry - represents a changelog entry
+
+=head1 DESCRIPTION
+
+This object represents a changelog entry. It is composed
+of a set of lines with specific purpose: an header line, changes lines, a
+trailer line. Blank lines can be between those kind of lines.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $entry = Dpkg::Changelog::Entry->new()
+
+Creates a new object. It doesn't represent a real changelog entry
+until one has been successfully parsed or built from scratch.
+
+=cut
+
+sub new {
+    my ($this) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+	header => undef,
+	changes => [],
+	trailer => undef,
+	blank_after_header => [],
+	blank_after_changes => [],
+	blank_after_trailer => [],
+    };
+    bless $self, $class;
+    return $self;
+}
+
+=item my $str = $entry->output()
+
+=item "$entry"
+
+Get a string representation of the changelog entry.
+
+=item $entry->output($fh)
+
+Print the string representation of the changelog entry to a
+filehandle.
+
+=cut
+
+sub _format_output_block {
+    my $lines = shift;
+    return join('', map { $_ . "\n" } @{$lines});
+}
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str = '';
+    $str .= $self->{header} . "\n" if defined($self->{header});
+    $str .= _format_output_block($self->{blank_after_header});
+    $str .= _format_output_block($self->{changes});
+    $str .= _format_output_block($self->{blank_after_changes});
+    $str .= $self->{trailer} . "\n" if defined($self->{trailer});
+    $str .= _format_output_block($self->{blank_after_trailer});
+    print { $fh } $str if defined $fh;
+    return $str;
+}
+
+=item $entry->get_part($part)
+
+Return either a string (for a single line) or an array ref (for multiple
+lines) corresponding to the requested part. $part can be
+"header, "changes", "trailer", "blank_after_header",
+"blank_after_changes", "blank_after_trailer".
+
+=cut
+
+sub get_part {
+    my ($self, $part) = @_;
+    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
+    return $self->{$part};
+}
+
+=item $entry->set_part($part, $value)
+
+Set the value of the corresponding part. $value can be a string
+or an array ref.
+
+=cut
+
+sub set_part {
+    my ($self, $part, $value) = @_;
+    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
+    if (ref($self->{$part})) {
+	if (ref($value)) {
+	    $self->{$part} = $value;
+	} else {
+	    $self->{$part} = [ $value ];
+	}
+    } else {
+	$self->{$part} = $value;
+    }
+}
+
+=item $entry->extend_part($part, $value)
+
+Concatenate $value at the end of the part. If the part is already a
+multi-line value, $value is added as a new line otherwise it's
+concatenated at the end of the current line.
+
+=cut
+
+sub extend_part {
+    my ($self, $part, $value, @rest) = @_;
+    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
+    if (ref($self->{$part})) {
+	if (ref($value)) {
+	    push @{$self->{$part}}, @$value;
+	} else {
+	    push @{$self->{$part}}, $value;
+	}
+    } else {
+	if (defined($self->{$part})) {
+	    if (ref($value)) {
+		$self->{$part} = [ $self->{$part}, @$value ];
+	    } else {
+		$self->{$part} .= $value;
+	    }
+	} else {
+	    $self->{$part} = $value;
+	}
+    }
+}
+
+=item $is_empty = $entry->is_empty()
+
+Returns 1 if the changelog entry doesn't contain anything at all.
+Returns 0 as soon as it contains something in any of its non-blank
+parts.
+
+=cut
+
+sub is_empty {
+    my ($self) = @_;
+    return !(defined($self->{header}) || defined($self->{trailer}) ||
+	     scalar(@{$self->{changes}}));
+}
+
+=item $entry->normalize()
+
+Normalize the content. Strip whitespaces at end of lines, use a single
+empty line to separate each part.
+
+=cut
+
+sub normalize {
+    my ($self) = @_;
+    if (defined($self->{header})) {
+	$self->{header} =~ s/\s+$//g;
+	$self->{blank_after_header} = [''];
+    } else {
+	$self->{blank_after_header} = [];
+    }
+    if (scalar(@{$self->{changes}})) {
+	s/\s+$//g foreach @{$self->{changes}};
+	$self->{blank_after_changes} = [''];
+    } else {
+	$self->{blank_after_changes} = [];
+    }
+    if (defined($self->{trailer})) {
+	$self->{trailer} =~ s/\s+$//g;
+	$self->{blank_after_trailer} = [''];
+    } else {
+	$self->{blank_after_trailer} = [];
+    }
+}
+
+=item my $src = $entry->get_source()
+
+Return the name of the source package associated to the changelog entry.
+
+=cut
+
+sub get_source {
+    return;
+}
+
+=item my $ver = $entry->get_version()
+
+Return the version associated to the changelog entry.
+
+=cut
+
+sub get_version {
+    return;
+}
+
+=item my @dists = $entry->get_distributions()
+
+Return a list of target distributions for this version.
+
+=cut
+
+sub get_distributions {
+    return;
+}
+
+=item $fields = $entry->get_optional_fields()
+
+Return a set of optional fields exposed by the changelog entry.
+It always returns a Dpkg::Control object (possibly empty though).
+
+=cut
+
+sub get_optional_fields {
+    return Dpkg::Control::Changelog->new();
+}
+
+=item $urgency = $entry->get_urgency()
+
+Return the urgency of the associated upload.
+
+=cut
+
+sub get_urgency {
+    return;
+}
+
+=item my $maint = $entry->get_maintainer()
+
+Return the string identifying the person who signed this changelog entry.
+
+=cut
+
+sub get_maintainer {
+    return;
+}
+
+=item my $time = $entry->get_timestamp()
+
+Return the timestamp of the changelog entry.
+
+=cut
+
+sub get_timestamp {
+    return;
+}
+
+=item my $str = $entry->get_dpkg_changes()
+
+Returns a string that is suitable for usage in a C<Changes> field
+in the output format of C<dpkg-parsechangelog>.
+
+=cut
+
+sub get_dpkg_changes {
+    my ($self) = @_;
+    my $header = $self->get_part('header') || '';
+    $header =~ s/\s+$//;
+    return "\n$header\n\n" . join("\n", @{$self->get_part('changes')});
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry/Debian.pm b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry/Debian.pm
new file mode 100644
index 0000000..8d05953
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Entry/Debian.pm
@@ -0,0 +1,323 @@
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Changelog::Entry::Debian;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Exporter qw(import);
+use Dpkg::Changelog::Entry;
+use parent qw(Dpkg::Changelog::Entry);
+our @EXPORT_OK = qw(match_header match_trailer find_closes
+                    $regex_header $regex_trailer);
+
+use Date::Parse;
+
+use Dpkg::Gettext;
+use Dpkg::Control::Fields;
+use Dpkg::Control::Changelog;
+use Dpkg::Version;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Changelog::Entry::Debian - represents a Debian changelog entry
+
+=head1 DESCRIPTION
+
+This object represents a Debian changelog entry. It implements the
+generic interface Dpkg::Changelog::Entry. Only functions specific to this
+implementation are described below.
+
+=cut
+
+my $name_chars = qr/[-+0-9a-z.]/i;
+
+# XXX: Backwards compatibility, stop exporting on VERSION 2.00.
+## no critic (Variables::ProhibitPackageVars)
+
+# The matched content is the source package name ($1), the version ($2),
+# the target distributions ($3) and the options on the rest of the line ($4).
+our $regex_header = qr/^(\w$name_chars*) \(([^\(\) \t]+)\)((?:\s+$name_chars+)+)\;(.*?)\s*$/i;
+
+# The matched content is the maintainer name ($1), its email ($2),
+# some blanks ($3) and the timestamp ($4).
+our $regex_trailer = qr/^ \-\- (.*) <(.*)>(  ?)((\w+\,\s*)?\d{1,2}\s+\w+\s+\d{4}\s+\d{1,2}:\d\d:\d\d\s+[-+]\d{4}(\s+\([^\\\(\)]\))?)\s*$/o;
+
+## use critic
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my @items = $entry->get_change_items()
+
+Return a list of change items. Each item contains at least one line.
+A change line starting with an asterisk denotes the start of a new item.
+Any change line like "[ Raphaël Hertzog ]" is treated like an item of its
+own even if it starts a set of items attributed to this person (the
+following line necessarily starts a new item).
+
+=cut
+
+sub get_change_items {
+    my ($self) = @_;
+    my (@items, @blanks, $item);
+    foreach my $line (@{$self->get_part('changes')}) {
+	if ($line =~ /^\s*\*/) {
+	    push @items, $item if defined $item;
+	    $item = "$line\n";
+	} elsif ($line =~ /^\s*\[\s[^\]]+\s\]\s*$/) {
+	    push @items, $item if defined $item;
+	    push @items, "$line\n";
+	    $item = undef;
+	    @blanks = ();
+	} elsif ($line =~ /^\s*$/) {
+	    push @blanks, "$line\n";
+	} else {
+	    if (defined $item) {
+		$item .= "@blanks$line\n";
+	    } else {
+		$item = "$line\n";
+	    }
+	    @blanks = ();
+	}
+    }
+    push @items, $item if defined $item;
+    return @items;
+}
+
+=item my @errors = $entry->check_header()
+
+=item my @errors = $entry->check_trailer()
+
+Return a list of errors. Each item in the list is an error message
+describing the problem. If the empty list is returned, no errors
+have been found.
+
+=cut
+
+sub check_header {
+    my ($self) = @_;
+    my @errors;
+    if (defined($self->{header}) and $self->{header} =~ $regex_header) {
+	my ($version, $options) = ($2, $4);
+	$options =~ s/^\s+//;
+	my %optdone;
+	foreach my $opt (split(/\s*,\s*/, $options)) {
+	    unless ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
+		push @errors, sprintf(_g("bad key-value after \`;': \`%s'"), $opt);
+		next;
+	    }
+	    my ($k, $v) = (field_capitalize($1), $2);
+	    if ($optdone{$k}) {
+		push @errors, sprintf(_g('repeated key-value %s'), $k);
+	    }
+	    $optdone{$k} = 1;
+	    if ($k eq 'Urgency') {
+		push @errors, sprintf(_g('badly formatted urgency value: %s'), $v)
+		    unless ($v =~ m/^([-0-9a-z]+)((\s+.*)?)$/i);
+	    } elsif ($k eq 'Binary-Only') {
+		push @errors, sprintf(_g('bad binary-only value: %s'), $v)
+		    unless ($v eq 'yes');
+	    } elsif ($k =~ m/^X[BCS]+-/i) {
+	    } else {
+		push @errors, sprintf(_g('unknown key-value %s'), $k);
+	    }
+	}
+	my ($ok, $msg) = version_check($version);
+	unless ($ok) {
+	    push @errors, sprintf(_g("version '%s' is invalid: %s"), $version, $msg);
+	}
+    } else {
+	push @errors, _g("the header doesn't match the expected regex");
+    }
+    return @errors;
+}
+
+sub check_trailer {
+    my ($self) = @_;
+    my @errors;
+    if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
+	if ($3 ne '  ') {
+	    push @errors, _g('badly formatted trailer line');
+	}
+	unless (defined str2time($4)) {
+	    push @errors, sprintf(_g("couldn't parse date %s"), $4);
+	}
+    } else {
+	push @errors, _g("the trailer doesn't match the expected regex");
+    }
+    return @errors;
+}
+
+=item $entry->normalize()
+
+Normalize the content. Strip whitespaces at end of lines, use a single
+empty line to separate each part.
+
+=cut
+
+sub normalize {
+    my ($self) = @_;
+    $self->SUPER::normalize();
+    #XXX: recreate header/trailer
+}
+
+sub get_source {
+    my ($self) = @_;
+    if (defined($self->{header}) and $self->{header} =~ $regex_header) {
+	return $1;
+    }
+    return;
+}
+
+sub get_version {
+    my ($self) = @_;
+    if (defined($self->{header}) and $self->{header} =~ $regex_header) {
+	return Dpkg::Version->new($2);
+    }
+    return;
+}
+
+sub get_distributions {
+    my ($self) = @_;
+    if (defined($self->{header}) and $self->{header} =~ $regex_header) {
+	my $value = $3;
+	$value =~ s/^\s+//;
+	my @dists = split(/\s+/, $value);
+	return @dists if wantarray;
+	return $dists[0];
+    }
+    return;
+}
+
+sub get_optional_fields {
+    my ($self) = @_;
+    my $f = Dpkg::Control::Changelog->new();
+    if (defined($self->{header}) and $self->{header} =~ $regex_header) {
+	my $options = $4;
+	$options =~ s/^\s+//;
+	foreach my $opt (split(/\s*,\s*/, $options)) {
+	    if ($opt =~ m/^([-0-9a-z]+)\=\s*(.*\S)$/i) {
+		$f->{$1} = $2;
+	    }
+	}
+    }
+    my @closes = find_closes(join("\n", @{$self->{changes}}));
+    if (@closes) {
+	$f->{Closes} = join(' ', @closes);
+    }
+    return $f;
+}
+
+sub get_urgency {
+    my ($self) = @_;
+    my $f = $self->get_optional_fields();
+    if (exists $f->{Urgency}) {
+	$f->{Urgency} =~ s/\s.*$//;
+	return lc($f->{Urgency});
+    }
+    return;
+}
+
+sub get_maintainer {
+    my ($self) = @_;
+    if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
+	return "$1 <$2>";
+    }
+    return;
+}
+
+sub get_timestamp {
+    my ($self) = @_;
+    if (defined($self->{trailer}) and $self->{trailer} =~ $regex_trailer) {
+	return $4;
+    }
+    return;
+}
+
+=back
+
+=head1 UTILITY FUNCTIONS
+
+=over 4
+
+=item my $bool = match_header($line)
+
+Checks if the line matches a valid changelog header line.
+
+=cut
+
+sub match_header {
+    my ($line) = @_;
+
+    return $line =~ /$regex_header/;
+}
+
+=item my $bool = match_trailer($line)
+
+Checks if the line matches a valid changelog trailing line.
+
+=cut
+
+sub match_trailer {
+    my ($line) = @_;
+
+    return $line =~ /$regex_trailer/;
+}
+
+=item my @closed_bugs = find_closes($changes)
+
+Takes one string as argument and finds "Closes: #123456, #654321" statements
+as supported by the Debian Archive software in it. Returns all closed bug
+numbers in an array.
+
+=cut
+
+sub find_closes {
+    my $changes = shift;
+    my %closes;
+
+    while ($changes &&
+           ($changes =~ /closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*/ig)) {
+        $closes{$_} = 1 foreach($& =~ /\#?\s?(\d+)/g);
+    }
+
+    my @closes = sort { $a <=> $b } keys %closes;
+    return @closes;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New functions: match_header(), match_trailer()
+Deprecated variables: $regex_header, $regex_trailer
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Changelog/Parse.pm b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Parse.pm
new file mode 100644
index 0000000..41c4440d
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Changelog/Parse.pm
@@ -0,0 +1,167 @@
+# Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
+# Copyright © 2009       Raphaël Hertzog <hertzog@debian.org>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Changelog::Parse - generic changelog parser for dpkg-parsechangelog
+
+=head1 DESCRIPTION
+
+This module provides a single function changelog_parse() which reproduces
+all the features of dpkg-parsechangelog.
+
+=head2 FUNCTIONS
+
+=cut
+
+package Dpkg::Changelog::Parse;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Changelog;
+
+use Exporter qw(import);
+our @EXPORT = qw(changelog_parse);
+
+=over 4
+
+=item my $fields = changelog_parse(%opt)
+
+This function will parse a changelog. In list context, it return as many
+Dpkg::Control object as the parser did output. In scalar context, it will
+return only the first one. If the parser didn't return any data, it will
+return an empty in list context or undef on scalar context. If the parser
+failed, it will die.
+
+The parsing itself is done by an external program (searched in the
+following list of directories: $opt{libdir},
+F</usr/local/lib/dpkg/parsechangelog>, F</usr/lib/dpkg/parsechangelog>) That
+program is named according to the format that it's able to parse. By
+default it's either "debian" or the format name lookep up in the 40 last
+lines of the changelog itself (extracted with this perl regular expression
+"\schangelog-format:\s+([0-9a-z]+)\W"). But it can be overridden
+with $opt{changelogformat}. The program expects the content of the
+changelog file on its standard input.
+
+The changelog file that is parsed is F<debian/changelog> by default but it
+can be overridden with $opt{file}.
+
+All the other keys in %opt are forwarded as parameter to the external
+parser. If the key starts with "-", it's passed as is. If not, it's passed
+as "--<key>". If the value of the corresponding hash entry is defined, then
+it's passed as the parameter that follows.
+
+=cut
+
+sub changelog_parse {
+    my (%options) = @_;
+    my @parserpath = ('/usr/local/lib/dpkg/parsechangelog',
+                      "$Dpkg::LIBDIR/parsechangelog",
+                      '/usr/lib/dpkg/parsechangelog');
+    my $format = 'debian';
+    my $force = 0;
+
+    # Extract and remove options that do not concern the changelog parser
+    # itself (and that we shouldn't forward)
+    if (exists $options{libdir}) {
+	unshift @parserpath, $options{libdir};
+	delete $options{libdir};
+    }
+    if (exists $options{changelogformat}) {
+	$format = $options{changelogformat};
+	delete $options{changelogformat};
+	$force = 1;
+    }
+
+    # Set a default filename
+    if (not exists $options{file}) {
+	$options{file} = 'debian/changelog';
+    }
+    my $changelogfile = $options{file};
+
+    # Extract the format from the changelog file if possible
+    unless($force or ($changelogfile eq '-')) {
+	open(my $format_fh, '-|', 'tail', '-n', '40', $changelogfile)
+	    or syserr(_g('cannot create pipe for %s'), 'tail');
+	while (<$format_fh>) {
+	    $format = $1 if m/\schangelog-format:\s+([0-9a-z]+)\W/;
+	}
+	close($format_fh) or subprocerr(_g('tail of %s'), $changelogfile);
+    }
+
+    # Find the right changelog parser
+    my $parser;
+    foreach my $dir (@parserpath) {
+        my $candidate = "$dir/$format";
+	next if not -e $candidate;
+	if (-x _) {
+	    $parser = $candidate;
+	    last;
+	} else {
+	    warning(_g('format parser %s not executable'), $candidate);
+	}
+    }
+    error(_g('changelog format %s is unknown'), $format) if not defined $parser;
+
+    # Create the arguments for the changelog parser
+    my @exec = ($parser, "-l$changelogfile");
+    foreach (keys %options) {
+	if (m/^-/) {
+	    # Options passed untouched
+	    push @exec, $_;
+	} else {
+	    # Non-options are mapped to long options
+	    push @exec, "--$_";
+	}
+	push @exec, $options{$_} if defined($options{$_});
+    }
+
+    # Fork and call the parser
+    my $pid = open(my $parser_fh, '-|');
+    syserr(_g('cannot fork for %s'), $parser) unless defined $pid;
+    if (not $pid) {
+	exec(@exec) or syserr(_g('cannot exec format parser: %s'), $parser);
+    }
+
+    # Get the output into several Dpkg::Control objects
+    my (@res, $fields);
+    while (1) {
+        $fields = Dpkg::Control::Changelog->new();
+        last unless $fields->parse($parser_fh, _g('output of changelog parser'));
+	push @res, $fields;
+    }
+    close($parser_fh) or subprocerr(_g('changelog parser %s'), $parser);
+    if (wantarray) {
+	return @res;
+    } else {
+	return $res[0] if (@res);
+	return;
+    }
+}
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Checksums.pm b/third_party/dpkg-dev/scripts/Dpkg/Checksums.pm
new file mode 100644
index 0000000..5b6d0ab
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Checksums.pm
@@ -0,0 +1,378 @@
+# Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
+# Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Checksums;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::IPC;
+
+use Exporter qw(import);
+our @EXPORT = qw(checksums_get_list checksums_is_supported
+		 checksums_get_property);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Checksums - generate and manipulate file checksums
+
+=head1 DESCRIPTION
+
+This module provides an object that can generate and manipulate
+various file checksums as well as some methods to query information
+about supported checksums.
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=cut
+
+my $CHECKSUMS = {
+    md5 => {
+	program => [ 'md5sum' ],
+	regex => qr/[0-9a-f]{32}/,
+    },
+    sha1 => {
+	program => [ 'sha1sum' ],
+	regex => qr/[0-9a-f]{40}/,
+    },
+    sha256 => {
+	program => [ 'sha256sum' ],
+	regex => qr/[0-9a-f]{64}/,
+    },
+};
+
+=item @list = checksums_get_list()
+
+Returns the list of supported checksums algorithms.
+
+=cut
+
+sub checksums_get_list() {
+    my @list = sort keys %{$CHECKSUMS};
+    return @list;
+}
+
+=item $bool = checksums_is_supported($alg)
+
+Returns a boolean indicating whether the given checksum algorithm is
+supported. The checksum algorithm is case-insensitive.
+
+=cut
+
+sub checksums_is_supported($) {
+    my ($alg) = @_;
+    return exists $CHECKSUMS->{lc($alg)};
+}
+
+=item $value = checksums_get_property($alg, $property)
+
+Returns the requested property of the checksum algorithm. Returns undef if
+either the property or the checksum algorithm doesn't exist. Valid
+properties currently include "program" (returns an array reference with
+a program name and parameters required to compute the checksum of the
+filename given as last parameter) and "regex" for the regular expression
+describing the common string representation of the checksum (as output
+by the program that generates it).
+
+=cut
+
+sub checksums_get_property($$) {
+    my ($alg, $property) = @_;
+    return unless checksums_is_supported($alg);
+    return $CHECKSUMS->{lc($alg)}{$property};
+}
+
+=back
+
+=head1 OBJECT METHODS
+
+=over 4
+
+=item my $ck = Dpkg::Checksums->new()
+
+Create a new Dpkg::Checksums object. This object is able to store
+the checksums of several files to later export them or verify them.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {};
+    bless $self, $class;
+    $self->reset();
+
+    return $self;
+}
+
+=item $ck->reset()
+
+Forget about all checksums stored. The object is again in the same state
+as if it was newly created.
+
+=cut
+
+sub reset {
+    my ($self) = @_;
+    $self->{files} = [];
+    $self->{checksums} = {};
+    $self->{size} = {};
+}
+
+=item $ck->add_from_file($filename, %opts)
+
+Add checksums information for the file $filename. The file must exists
+for the call to succeed. If you don't want the given filename to appear
+when you later export the checksums you might want to set the "key"
+option with the public name that you want to use. Also if you don't want
+to generate all the checksums, you can pass an array reference of the
+wanted checksums in the "checksums" option.
+
+It the object already contains checksums information associated the
+filename (or key), it will error out if the newly computed information
+does not match what's stored.
+
+=cut
+
+sub add_from_file {
+    my ($self, $file, %opts) = @_;
+    my $key = exists $opts{key} ? $opts{key} : $file;
+    my @alg;
+    if (exists $opts{checksums}) {
+	push @alg, map { lc($_) } @{$opts{checksums}};
+    } else {
+	push @alg, checksums_get_list();
+    }
+
+    push @{$self->{files}}, $key unless exists $self->{size}{$key};
+    (my @s = stat($file)) or syserr(_g('cannot fstat file %s'), $file);
+    if (exists $self->{size}{$key} and $self->{size}{$key} != $s[7]) {
+	error(_g('file %s has size %u instead of expected %u'),
+	      $file, $s[7], $self->{size}{$key});
+    }
+    $self->{size}{$key} = $s[7];
+
+    foreach my $alg (@alg) {
+	my @exec = (@{$CHECKSUMS->{$alg}{program}}, $file);
+	my $regex = $CHECKSUMS->{$alg}{regex};
+	my $output;
+	spawn(exec => \@exec, to_string => \$output);
+	if ($output =~ /^($regex)(\s|$)/m) {
+	    my $newsum = $1;
+	    if (exists $self->{checksums}{$key}{$alg} and
+		$self->{checksums}{$key}{$alg} ne $newsum) {
+		error(_g('file %s has checksum %s instead of expected %s (algorithm %s)'),
+		      $file, $newsum, $self->{checksums}{$key}{$alg}, $alg);
+	    }
+	    $self->{checksums}{$key}{$alg} = $newsum;
+	} else {
+	    error(_g("checksum program gave bogus output `%s'"), $output);
+	}
+    }
+}
+
+=item $ck->add_from_string($alg, $value)
+
+Add checksums of type $alg that are stored in the $value variable.
+$value can be multi-lines, each line should be a space separated list
+of checksum, file size and filename. Leading or trailing spaces are
+not allowed.
+
+It the object already contains checksums information associated to the
+filenames, it will error out if the newly read information does not match
+what's stored.
+
+=cut
+
+sub add_from_string {
+    my ($self, $alg, $fieldtext) = @_;
+    $alg = lc($alg);
+    my $rx_fname = qr/[0-9a-zA-Z][-+:.,=0-9a-zA-Z_~]+/;
+    my $regex = checksums_get_property($alg, 'regex');
+    my $checksums = $self->{checksums};
+
+    for my $checksum (split /\n */, $fieldtext) {
+	next if $checksum eq '';
+	unless ($checksum =~ m/^($regex)\s+(\d+)\s+($rx_fname)$/) {
+	    error(_g('invalid line in %s checksums string: %s'),
+		  $alg, $checksum);
+	}
+	my ($sum, $size, $file) = ($1, $2, $3);
+	if (exists($checksums->{$file}{$alg})
+	    and $checksums->{$file}{$alg} ne $sum) {
+	    error(_g("conflicting checksums '%s' and '%s' for file '%s'"),
+		  $checksums->{$file}{$alg}, $sum, $file);
+	}
+	if (exists $self->{size}{$file} and $self->{size}{$file} != $size) {
+	    error(_g("conflicting file sizes '%u' and '%u' for file '%s'"),
+		  $self->{size}{$file}, $size, $file);
+	}
+	push @{$self->{files}}, $file unless exists $self->{size}{$file};
+	$checksums->{$file}{$alg} = $sum;
+	$self->{size}{$file} = $size;
+    }
+}
+
+=item $ck->add_from_control($control, %opts)
+
+Read checksums from Checksums-* fields stored in the Dpkg::Control object
+$control. It uses $self->add_from_string() on the field values to do the
+actual work.
+
+If the option "use_files_for_md5" evaluates to true, then the "Files"
+field is used in place of the "Checksums-Md5" field. By default the option
+is false.
+
+=cut
+
+sub add_from_control {
+    my ($self, $control, %opts) = @_;
+    $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5};
+    foreach my $alg (checksums_get_list()) {
+	my $key = "Checksums-$alg";
+	$key = 'Files' if ($opts{use_files_for_md5} and $alg eq 'md5');
+	if (exists $control->{$key}) {
+	    $self->add_from_string($alg, $control->{$key});
+	}
+    }
+}
+
+=item @files = $ck->get_files()
+
+Return the list of files whose checksums are stored in the object.
+
+=cut
+
+sub get_files {
+    my ($self) = @_;
+    return @{$self->{files}};
+}
+
+=item $bool = $ck->has_file($file)
+
+Return true if we have checksums for the given file. Returns false
+otherwise.
+
+=cut
+
+sub has_file {
+    my ($self, $file) = @_;
+    return exists $self->{size}{$file};
+}
+
+=item $ck->remove_file($file)
+
+Remove all checksums of the given file.
+
+=cut
+
+sub remove_file {
+    my ($self, $file) = @_;
+    return unless $self->has_file($file);
+    delete $self->{checksums}{$file};
+    delete $self->{size}{$file};
+    @{$self->{files}} = grep { $_ ne $file } $self->get_files();
+}
+
+=item $checksum = $ck->get_checksum($file, $alg)
+
+Return the checksum of type $alg for the requested $file. This will not
+compute the checksum but only return the checksum stored in the object, if
+any.
+
+If $alg is not defined, it returns a reference to a hash: keys are
+the checksum algorithms and values are the checksums themselves. The
+hash returned must not be modified, it's internal to the object.
+
+=cut
+
+sub get_checksum {
+    my ($self, $file, $alg) = @_;
+    $alg = lc($alg) if defined $alg;
+    if (exists $self->{checksums}{$file}) {
+	return $self->{checksums}{$file} unless defined $alg;
+	return $self->{checksums}{$file}{$alg};
+    }
+    return;
+}
+
+=item $size = $ck->get_size($file)
+
+Return the size of the requested file if it's available in the object.
+
+=cut
+
+sub get_size {
+    my ($self, $file) = @_;
+    return $self->{size}{$file};
+}
+
+=item $ck->export_to_string($alg, %opts)
+
+Return a multi-line string containing the checksums of type $alg. The
+string can be stored as-is in a Checksum-* field of a Dpkg::Control
+object.
+
+=cut
+
+sub export_to_string {
+    my ($self, $alg, %opts) = @_;
+    my $res = '';
+    foreach my $file ($self->get_files()) {
+	my $sum = $self->get_checksum($file, $alg);
+	my $size = $self->get_size($file);
+	next unless defined $sum and defined $size;
+	$res .= "\n$sum $size $file";
+    }
+    return $res;
+}
+
+=item $ck->export_to_control($control, %opts)
+
+Export the checksums in the Checksums-* fields of the Dpkg::Control
+$control object.
+
+=cut
+
+sub export_to_control {
+    my ($self, $control, %opts) = @_;
+    $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5};
+    foreach my $alg (checksums_get_list()) {
+	my $key = "Checksums-$alg";
+	$key = 'Files' if ($opts{use_files_for_md5} and $alg eq 'md5');
+	$control->{$key} = $self->export_to_string($alg, %opts);
+    }
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Compression.pm b/third_party/dpkg-dev/scripts/Dpkg/Compression.pm
new file mode 100644
index 0000000..3fe638d
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Compression.pm
@@ -0,0 +1,244 @@
+# Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2010-2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Compression;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use Exporter qw(import);
+our @EXPORT = qw($compression_re_file_ext compression_get_list
+		 compression_is_supported compression_get_property
+		 compression_guess_from_filename
+		 compression_get_file_extension_regex
+		 compression_get_default compression_set_default
+		 compression_get_default_level
+		 compression_set_default_level
+		 compression_is_valid_level);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Compression - simple database of available compression methods
+
+=head1 DESCRIPTION
+
+This modules provides a few public funcions and a public regex to
+interact with the set of supported compression methods.
+
+=cut
+
+my $COMP = {
+    gzip => {
+	file_ext => 'gz',
+	comp_prog => [ 'gzip', '--no-name', '--rsyncable' ],
+	decomp_prog => [ 'gunzip' ],
+	default_level => 9,
+    },
+    bzip2 => {
+	file_ext => 'bz2',
+	comp_prog => [ 'bzip2' ],
+	decomp_prog => [ 'bunzip2' ],
+	default_level => 9,
+    },
+    lzma => {
+	file_ext => 'lzma',
+	comp_prog => [ 'xz', '--format=lzma' ],
+	decomp_prog => [ 'unxz', '--format=lzma' ],
+	default_level => 6,
+    },
+    xz => {
+	file_ext => 'xz',
+	comp_prog => [ 'xz' ],
+	decomp_prog => [ 'unxz' ],
+	default_level => 6,
+    },
+};
+
+# XXX: Backwards compatibility, stop exporting on VERSION 2.00.
+## no critic (Variables::ProhibitPackageVars)
+our $default_compression = 'gzip';
+our $default_compression_level = undef;
+
+my $regex = join '|', map { $_->{file_ext} } values %$COMP;
+our $compression_re_file_ext = qr/(?:$regex)/;
+## use critic
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item my @list = compression_get_list()
+
+Returns a list of supported compression methods (sorted alphabetically).
+
+=cut
+
+sub compression_get_list {
+    my @list = sort keys %$COMP;
+    return @list;
+}
+
+=item compression_is_supported($comp)
+
+Returns a boolean indicating whether the give compression method is
+known and supported.
+
+=cut
+
+sub compression_is_supported {
+    return exists $COMP->{$_[0]};
+}
+
+=item compression_get_property($comp, $property)
+
+Returns the requested property of the compression method. Returns undef if
+either the property or the compression method doesn't exist. Valid
+properties currently include "file_ext" for the file extension,
+"default_level" for the default compression level,
+"comp_prog" for the name of the compression program and "decomp_prog" for
+the name of the decompression program.
+
+=cut
+
+sub compression_get_property {
+    my ($comp, $property) = @_;
+    return unless compression_is_supported($comp);
+    return $COMP->{$comp}{$property} if exists $COMP->{$comp}{$property};
+    return;
+}
+
+=item compression_guess_from_filename($filename)
+
+Returns the compression method that is likely used on the indicated
+filename based on its file extension.
+
+=cut
+
+sub compression_guess_from_filename {
+    my $filename = shift;
+    foreach my $comp (compression_get_list()) {
+	my $ext = compression_get_property($comp, 'file_ext');
+        if ($filename =~ /^(.*)\.\Q$ext\E$/) {
+	    return $comp;
+        }
+    }
+    return;
+}
+
+=item my $regex = compression_get_file_extension_regex()
+
+Returns a regex that matches a file extension of a file compressed with
+one of the supported compression methods.
+
+=cut
+
+sub compression_get_file_extension_regex {
+    return $compression_re_file_ext;
+}
+
+=item my $comp = compression_get_default()
+
+Return the default compression method. It's "gzip" unless
+C<compression_set_default> has been used to change it.
+
+=item compression_set_default($comp)
+
+Change the default compression method. Errors out if the
+given compression method is not supported.
+
+=cut
+
+sub compression_get_default {
+    return $default_compression;
+}
+
+sub compression_set_default {
+    my ($method) = @_;
+    error(_g('%s is not a supported compression'), $method)
+            unless compression_is_supported($method);
+    $default_compression = $method;
+}
+
+=item my $level = compression_get_default_level()
+
+Return the default compression level used when compressing data. It's "9"
+for "gzip" and "bzip2", "6" for "xz" and "lzma", unless
+C<compression_set_default_level> has been used to change it.
+
+=item compression_set_default_level($level)
+
+Change the default compression level. Passing undef as the level will
+reset it to the compressor specific default, otherwise errors out if the
+level is not valid (see C<compression_is_valid_level>).
+
+=cut
+
+sub compression_get_default_level {
+    if (defined $default_compression_level) {
+        return $default_compression_level;
+    } else {
+        return compression_get_property($default_compression, 'default_level');
+    }
+}
+
+sub compression_set_default_level {
+    my ($level) = @_;
+    error(_g('%s is not a compression level'), $level)
+        if defined($level) and not compression_is_valid_level($level);
+    $default_compression_level = $level;
+}
+
+=item compression_is_valid_level($level)
+
+Returns a boolean indicating whether $level is a valid compression level
+(it must be either a number between 1 and 9 or "fast" or "best")
+
+=cut
+
+sub compression_is_valid_level {
+    my ($level) = @_;
+    return $level =~ /^([1-9]|fast|best)$/;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.02
+
+New function: compression_get_file_extension_regex()
+
+Deprecated variables: $default_compression, $default_compression_level
+and $compression_re_file_ext
+
+=head2 Version 1.01
+
+Default compression level is not global any more, it is per compressor type.
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Compression/FileHandle.pm b/third_party/dpkg-dev/scripts/Dpkg/Compression/FileHandle.pm
new file mode 100644
index 0000000..a214ea4a
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Compression/FileHandle.pm
@@ -0,0 +1,451 @@
+# Copyright © 2008-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Compression::FileHandle;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Compression;
+use Dpkg::Compression::Process;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+
+use Carp;
+use POSIX qw(:signal_h :sys_wait_h);
+
+use parent qw(FileHandle Tie::Handle);
+
+# Useful reference to understand some kludges required to
+# have the object behave like a filehandle
+# http://blog.woobling.org/2009/10/are-filehandles-objects.html
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Compression::FileHandle - object dealing transparently with file compression
+
+=head1 SYNOPSIS
+
+    use Dpkg::Compression::FileHandle;
+
+    $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
+    print $fh "Something\n";
+    close $fh;
+
+    $fh = Dpkg::Compression::FileHandle->new();
+    open($fh, '>', 'sample.bz2');
+    print $fh "Something\n";
+    close $fh;
+
+    $fh = Dpkg::Compression::FileHandle->new();
+    $fh->open('sample.xz', 'w');
+    $fh->print("Something\n");
+    $fh->close();
+
+    $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
+    my @lines = <$fh>;
+    close $fh;
+
+    $fh = Dpkg::Compression::FileHandle->new();
+    open($fh, '<', 'sample.bz2');
+    my @lines = <$fh>;
+    close $fh;
+
+    $fh = Dpkg::Compression::FileHandle->new();
+    $fh->open('sample.xz', 'r');
+    my @lines = $fh->getlines();
+    $fh->close();
+
+=head1 DESCRIPTION
+
+Dpkg::Compression::FileHandle is an object that can be used
+like any filehandle and that deals transparently with compressed
+files. By default, the compression scheme is guessed from the filename
+but you can override this behaviour with the method C<set_compression>.
+
+If you don't open the file explicitly, it will be auto-opened on the
+first read or write operation based on the filename set at creation time
+(or later with the C<set_filename> method).
+
+Once a file has been opened, the filehandle must be closed before being
+able to open another file.
+
+=head1 STANDARD FUNCTIONS
+
+The standard functions acting on filehandles should accept a
+Dpkg::Compression::FileHandle object transparently including
+C<open> (only when using the variant with 3 parameters), C<close>,
+C<binmode>, C<eof>, C<fileno>, C<getc>, C<print>, C<printf>, C<read>,
+C<sysread>, C<say>, C<write>, C<syswrite>, C<seek>, C<sysseek>, C<tell>.
+
+Note however that C<seek> and C<sysseek> will only work on uncompressed
+files as compressed files are really pipes to the compressor programs
+and you can't seek on a pipe.
+
+=head1 FileHandle METHODS
+
+The object inherits from FileHandle so all methods that work on this
+object should work for Dpkg::Compression::FileHandle too. There
+may be exceptions though.
+
+=head1 PUBLIC METHODS
+
+=over 4
+
+=item my $fh = Dpkg::Compression::FileHandle->new(%opts)
+
+Creates a new filehandle supporting on-the-fly compression/decompression.
+Supported options are "filename", "compression", "compression_level" (see
+respective set_* functions) and "add_comp_ext". If "add_comp_ext"
+evaluates to true, then the extension corresponding to the selected
+compression scheme is automatically added to the recorded filename. It's
+obviously incompatible with automatic detection of the compression method.
+
+=cut
+
+# Object methods
+sub new {
+    my ($this, %args) = @_;
+    my $class = ref($this) || $this;
+    my $self = FileHandle->new();
+    # Tying is required to overload the open functions and to auto-open
+    # the file on first read/write operation
+    tie *$self, $class, $self;
+    bless $self, $class;
+    # Initializations
+    *$self->{compression} = 'auto';
+    *$self->{compressor} = Dpkg::Compression::Process->new();
+    *$self->{add_comp_ext} = $args{add_compression_extension} ||
+	    $args{add_comp_ext} || 0;
+    *$self->{allow_sigpipe} = 0;
+    if (exists $args{filename}) {
+	$self->set_filename($args{filename});
+    }
+    if (exists $args{compression}) {
+	$self->set_compression($args{compression});
+    }
+    if (exists $args{compression_level}) {
+	$self->set_compression_level($args{compression_level});
+    }
+    return $self;
+}
+
+=item $fh->ensure_open($mode)
+
+Ensure the file is opened in the requested mode ("r" for read and "w" for
+write). Opens the file with the recorded filename if needed. If the file
+is already open but not in the requested mode, then it errors out.
+
+=cut
+
+sub ensure_open {
+    my ($self, $mode) = @_;
+    if (exists *$self->{mode}) {
+	return if *$self->{mode} eq $mode;
+	croak "ensure_open requested incompatible mode: $mode";
+    } else {
+	if ($mode eq 'w') {
+	    $self->open_for_write();
+	} elsif ($mode eq 'r') {
+	    $self->open_for_read();
+	} else {
+	    croak "invalid mode in ensure_open: $mode";
+	}
+    }
+}
+
+##
+## METHODS FOR TIED HANDLE
+##
+sub TIEHANDLE {
+    my ($class, $self) = @_;
+    return $self;
+}
+
+sub WRITE {
+    my ($self, $scalar, $length, $offset) = @_;
+    $self->ensure_open('w');
+    return *$self->{file}->write($scalar, $length, $offset);
+}
+
+sub READ {
+    my ($self, $scalar, $length, $offset) = @_;
+    $self->ensure_open('r');
+    return *$self->{file}->read($scalar, $length, $offset);
+}
+
+sub READLINE {
+    my ($self) = shift;
+    $self->ensure_open('r');
+    return *$self->{file}->getlines() if wantarray;
+    return *$self->{file}->getline();
+}
+
+sub OPEN {
+    my ($self) = shift;
+    if (scalar(@_) == 2) {
+	my ($mode, $filename) = @_;
+	$self->set_filename($filename);
+	if ($mode eq '>') {
+	    $self->open_for_write();
+	} elsif ($mode eq '<') {
+	    $self->open_for_read();
+	} else {
+	    croak 'Dpkg::Compression::FileHandle does not support ' .
+	          "open() mode $mode";
+	}
+    } else {
+	croak 'Dpkg::Compression::FileHandle only supports open() ' .
+	      'with 3 parameters';
+    }
+    return 1; # Always works (otherwise errors out)
+}
+
+sub CLOSE {
+    my ($self) = shift;
+    my $ret = 1;
+    if (defined *$self->{file}) {
+	$ret = *$self->{file}->close(@_) if *$self->{file}->opened();
+    } else {
+	$ret = 0;
+    }
+    $self->cleanup();
+    return $ret;
+}
+
+sub FILENO {
+    my ($self) = shift;
+    return *$self->{file}->fileno(@_) if defined *$self->{file};
+    return;
+}
+
+sub EOF {
+    # Since perl 5.12, an integer parameter is passed describing how the
+    # function got called, just ignore it.
+    my ($self, $param) = (shift, shift);
+    return *$self->{file}->eof(@_) if defined *$self->{file};
+    return 1;
+}
+
+sub SEEK {
+    my ($self) = shift;
+    return *$self->{file}->seek(@_) if defined *$self->{file};
+    return 0;
+}
+
+sub TELL {
+    my ($self) = shift;
+    return *$self->{file}->tell(@_) if defined *$self->{file};
+    return -1;
+}
+
+sub BINMODE {
+    my ($self) = shift;
+    return *$self->{file}->binmode(@_) if defined *$self->{file};
+    return;
+}
+
+##
+## NORMAL METHODS
+##
+
+=item $fh->set_compression($comp)
+
+Defines the compression method used. $comp should one of the methods supported by
+B<Dpkg::Compression> or "none" or "auto". "none" indicates that the file is
+uncompressed and "auto" indicates that the method must be guessed based
+on the filename extension used.
+
+=cut
+
+sub set_compression {
+    my ($self, $method) = @_;
+    if ($method ne 'none' and $method ne 'auto') {
+	*$self->{compressor}->set_compression($method);
+    }
+    *$self->{compression} = $method;
+}
+
+=item $fh->set_compression_level($level)
+
+Indicate the desired compression level. It should be a value accepted
+by the function C<compression_is_valid_level> of B<Dpkg::Compression>.
+
+=cut
+
+sub set_compression_level {
+    my ($self, $level) = @_;
+    *$self->{compressor}->set_compression_level($level);
+}
+
+=item $fh->set_filename($name, [$add_comp_ext])
+
+Use $name as filename when the file must be opened/created. If
+$add_comp_ext is passed, it indicates whether the default extension
+of the compression method must be automatically added to the filename
+(or not).
+
+=cut
+
+sub set_filename {
+    my ($self, $filename, $add_comp_ext) = @_;
+    *$self->{filename} = $filename;
+    # Automatically add compression extension to filename
+    if (defined($add_comp_ext)) {
+	*$self->{add_comp_ext} = $add_comp_ext;
+    }
+    my $comp_ext_regex = compression_get_file_extension_regex();
+    if (*$self->{add_comp_ext} and $filename =~ /\.$comp_ext_regex$/) {
+	warning('filename %s already has an extension of a compressed file ' .
+	        'and add_comp_ext is active', $filename);
+    }
+}
+
+=item my $file = $fh->get_filename()
+
+Returns the filename that would be used when the filehandle must
+be opened (both in read and write mode). This function errors out
+if "add_comp_ext" is enabled while the compression method is set
+to "auto". The returned filename includes the extension of the compression
+method if "add_comp_ext" is enabled.
+
+=cut
+
+sub get_filename {
+    my $self = shift;
+    my $comp = *$self->{compression};
+    if (*$self->{add_comp_ext}) {
+	if ($comp eq 'auto') {
+	    croak 'automatic detection of compression is ' .
+	          'incompatible with add_comp_ext';
+	} elsif ($comp eq 'none') {
+	    return *$self->{filename};
+	} else {
+	    return *$self->{filename} . '.' .
+	           compression_get_property($comp, 'file_ext');
+	}
+    } else {
+	return *$self->{filename};
+    }
+}
+
+=item $ret = $fh->use_compression()
+
+Returns "0" if no compression is used and the compression method used
+otherwise. If the compression is set to "auto", the value returned
+depends on the extension of the filename obtained with the B<get_filename>
+method.
+
+=cut
+
+sub use_compression {
+    my ($self) = @_;
+    my $comp = *$self->{compression};
+    if ($comp eq 'none') {
+	return 0;
+    } elsif ($comp eq 'auto') {
+	$comp = compression_guess_from_filename($self->get_filename());
+	*$self->{compressor}->set_compression($comp) if $comp;
+    }
+    return $comp;
+}
+
+=item my $real_fh = $fh->get_filehandle()
+
+Returns the real underlying filehandle. Useful if you want to pass it
+along in a derived object.
+
+=cut
+
+sub get_filehandle {
+    my ($self) = @_;
+    return *$self->{file} if exists *$self->{file};
+}
+
+## INTERNAL METHODS
+
+sub open_for_write {
+    my ($self) = @_;
+    error("Can't reopen an already opened compressed file") if exists *$self->{mode};
+    my $filehandle;
+    if ($self->use_compression()) {
+	*$self->{compressor}->compress(from_pipe => \$filehandle,
+		to_file => $self->get_filename());
+    } else {
+	CORE::open($filehandle, '>', $self->get_filename)
+	    or syserr(_g('cannot write %s'), $self->get_filename());
+    }
+    *$self->{mode} = 'w';
+    *$self->{file} = $filehandle;
+}
+
+sub open_for_read {
+    my ($self) = @_;
+    error("Can't reopen an already opened compressed file") if exists *$self->{mode};
+    my $filehandle;
+    if ($self->use_compression()) {
+	*$self->{compressor}->uncompress(to_pipe => \$filehandle,
+		from_file => $self->get_filename());
+        *$self->{allow_sigpipe} = 1;
+    } else {
+	CORE::open($filehandle, '<', $self->get_filename)
+	    or syserr(_g('cannot read %s'), $self->get_filename());
+    }
+    *$self->{mode} = 'r';
+    *$self->{file} = $filehandle;
+}
+
+sub cleanup {
+    my ($self) = @_;
+    my $cmdline = *$self->{compressor}{cmdline} || '';
+    *$self->{compressor}->wait_end_process(nocheck => *$self->{allow_sigpipe});
+    if (*$self->{allow_sigpipe}) {
+        unless (($? == 0) || (WIFSIGNALED($?) && (WTERMSIG($?) == SIGPIPE))) {
+            subprocerr($cmdline);
+        }
+	*$self->{allow_sigpipe} = 0;
+    }
+    delete *$self->{mode};
+    delete *$self->{file};
+}
+
+=back
+
+=head1 DERIVED OBJECTS
+
+If you want to create an object that inherits from
+Dpkg::Compression::FileHandle you must be aware that
+the object is a reference to a GLOB that is returned by Symbol::gensym()
+and as such it's not a HASH.
+
+You can store internal data in a hash but you have to use
+C<*$self->{...}> to access the associated hash like in the example below:
+
+    sub set_option {
+	my ($self, $value) = @_;
+	*$self->{option} = $value;
+    }
+
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>
+
+=cut
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Compression/Process.pm b/third_party/dpkg-dev/scripts/Dpkg/Compression/Process.pm
new file mode 100644
index 0000000..03d6f276
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Compression/Process.pm
@@ -0,0 +1,207 @@
+# Copyright © 2008-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Compression::Process;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Carp;
+
+use Dpkg::Compression;
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::IPC;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Compression::Process - run compression/decompression processes
+
+=head1 DESCRIPTION
+
+This module provides an object oriented interface to run and manage
+compression/decompression processes.
+
+=head1 METHODS
+
+=over 4
+
+=item my $proc = Dpkg::Compression::Process->new(%opts)
+
+Create a new instance of the object. Supported options are "compression"
+and "compression_level" (see corresponding set_* functions).
+
+=cut
+
+sub new {
+    my ($this, %args) = @_;
+    my $class = ref($this) || $this;
+    my $self = {};
+    bless $self, $class;
+    $self->set_compression($args{compression} || compression_get_default());
+    $self->set_compression_level($args{compression_level} ||
+	    compression_get_default_level());
+    return $self;
+}
+
+=item $proc->set_compression($comp)
+
+Select the compression method to use. It errors out if the method is not
+supported according to C<compression_is_supported> (of
+B<Dpkg::Compression>).
+
+=cut
+
+sub set_compression {
+    my ($self, $method) = @_;
+    error(_g('%s is not a supported compression method'), $method)
+	    unless compression_is_supported($method);
+    $self->{compression} = $method;
+}
+
+=item $proc->set_compression_level($level)
+
+Select the compression level to use. It errors out if the level is not
+valid according to C<compression_is_valid_level> (of
+B<Dpkg::Compression>).
+
+=cut
+
+sub set_compression_level {
+    my ($self, $level) = @_;
+    error(_g('%s is not a compression level'), $level)
+	    unless compression_is_valid_level($level);
+    $self->{compression_level} = $level;
+}
+
+=item my @exec = $proc->get_compress_cmdline()
+
+=item my @exec = $proc->get_uncompress_cmdline()
+
+Returns a list ready to be passed to C<exec>, its first element is the
+program name (either for compression or decompression) and the following
+elements are parameters for the program.
+
+When executed the program acts as a filter between its standard input
+and its standard output.
+
+=cut
+
+sub get_compress_cmdline {
+    my ($self) = @_;
+    my @prog = (@{compression_get_property($self->{compression}, 'comp_prog')});
+    my $level = '-' . $self->{compression_level};
+    $level = '--' . $self->{compression_level}
+	    if $self->{compression_level} !~ m/^[1-9]$/;
+    push @prog, $level;
+    return @prog;
+}
+
+sub get_uncompress_cmdline {
+    my ($self) = @_;
+    return (@{compression_get_property($self->{compression}, 'decomp_prog')});
+}
+
+sub _sanity_check {
+    my ($self, %opts) = @_;
+    # Check for proper cleaning before new start
+    error(_g('Dpkg::Compression::Process can only start one subprocess at a time'))
+	    if $self->{pid};
+    # Check options
+    my $to = my $from = 0;
+    foreach (qw(file handle string pipe)) {
+        $to++ if $opts{"to_$_"};
+        $from++ if $opts{"from_$_"};
+    }
+    croak 'exactly one to_* parameter is needed' if $to != 1;
+    croak 'exactly one from_* parameter is needed' if $from != 1;
+    return %opts;
+}
+
+=item $proc->compress(%opts)
+
+Starts a compressor program. You must indicate where it will read its
+uncompressed data from and where it will write its compressed data to.
+This is accomplished by passing one parameter C<to_*> and one parameter
+C<from_*> as accepted by B<Dpkg::IPC::spawn>.
+
+You must call C<wait_end_process> after having called this method to
+properly close the sub-process (and verify that it exited without error).
+
+=cut
+
+sub compress {
+    my $self = shift;
+    my %opts = $self->_sanity_check(@_);
+    my @prog = $self->get_compress_cmdline();
+    $opts{exec} = \@prog;
+    $self->{cmdline} = "@prog";
+    $self->{pid} = spawn(%opts);
+    delete $self->{pid} if $opts{to_string}; # wait_child already done
+}
+
+=item $proc->uncompress(%opts)
+
+Starts a decompressor program. You must indicate where it will read its
+compressed data from and where it will write its uncompressed data to.
+This is accomplished by passing one parameter C<to_*> and one parameter
+C<from_*> as accepted by B<Dpkg::IPC::spawn>.
+
+You must call C<wait_end_process> after having called this method to
+properly close the sub-process (and verify that it exited without error).
+
+=cut
+
+sub uncompress {
+    my $self = shift;
+    my %opts = $self->_sanity_check(@_);
+    my @prog = $self->get_uncompress_cmdline();
+    $opts{exec} = \@prog;
+    $self->{cmdline} = "@prog";
+    $self->{pid} = spawn(%opts);
+    delete $self->{pid} if $opts{to_string}; # wait_child already done
+}
+
+=item $proc->wait_end_process(%opts)
+
+Call B<Dpkg::IPC::wait_child> to wait until the sub-process has exited
+and verify its return code. Any given option will be forwarded to
+the C<wait_child> function. Most notably you can use the "nocheck" option
+to verify the return code yourself instead of letting C<wait_child> do
+it for you.
+
+=cut
+
+sub wait_end_process {
+    my ($self, %opts) = @_;
+    $opts{cmdline} ||= $self->{cmdline};
+    wait_child($self->{pid}, %opts) if $self->{pid};
+    delete $self->{pid};
+    delete $self->{cmdline};
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Conf.pm b/third_party/dpkg-dev/scripts/Dpkg/Conf.pm
new file mode 100644
index 0000000..0825d62
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Conf.pm
@@ -0,0 +1,184 @@
+# Copyright © 2009-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Conf;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+
+use parent qw(Dpkg::Interface::Storable);
+
+use overload
+    '@{}' => sub { return [ $_[0]->get_options() ] },
+    fallback => 1;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Conf - parse dpkg configuration files
+
+=head1 DESCRIPTION
+
+The Dpkg::Conf object can be used to read options from a configuration
+file. It can exports an array that can then be parsed exactly like @ARGV.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $conf = Dpkg::Conf->new(%opts)
+
+Create a new Dpkg::Conf object. Some options can be set through %opts:
+if allow_short evaluates to true (it defaults to false), then short
+options are allowed in the configuration file, they should be prepended
+with a single hyphen.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+	options => [],
+	allow_short => 0,
+    };
+    foreach my $opt (keys %opts) {
+	$self->{$opt} = $opts{$opt};
+    }
+    bless $self, $class;
+
+    return $self;
+}
+
+=item @$conf
+
+=item @options = $conf->get_options()
+
+Returns the list of options that can be parsed like @ARGV.
+
+=cut
+
+sub get_options {
+    my ($self) = @_;
+    return @{$self->{options}};
+}
+
+=item $conf->load($file)
+
+Read options from a file. Return the number of options parsed.
+
+=item $conf->parse($fh)
+
+Parse options from a file handle. Return the number of options parsed.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $desc) = @_;
+    my $count = 0;
+    while (<$fh>) {
+	chomp;
+	s/^\s+//; s/\s+$//;   # Strip leading/trailing spaces
+	s/\s+=\s+/=/;         # Remove spaces around the first =
+	s/\s+/=/ unless m/=/; # First spaces becomes = if no =
+	next if /^#/ or /^$/; # Skip empty lines and comments
+	if (/^-[^-]/ and not $self->{allow_short}) {
+	    warning(_g('short option not allowed in %s, line %d'), $desc, $.);
+	    next;
+	}
+	if (/^([^=]+)(?:=(.*))?$/) {
+	    my ($name, $value) = ($1, $2);
+	    $name = "--$name" unless $name =~ /^-/;
+	    if (defined $value) {
+		$value =~ s/^"(.*)"$/$1/ or $value =~ s/^'(.*)'$/$1/;
+		push @{$self->{options}}, "$name=$value";
+	    } else {
+		push @{$self->{options}}, $name;
+	    }
+	    $count++;
+	} else {
+	    warning(_g('invalid syntax for option in %s, line %d'), $desc, $.);
+	}
+    }
+    return $count;
+}
+
+=item $conf->filter(remove => $rmfunc)
+
+=item $conf->filter(keep => $keepfunc)
+
+Filter the list of options, either removing or keeping all those that
+return true when &$rmfunc($option) or &keepfunc($option) is called.
+
+=cut
+
+sub filter {
+    my ($self, %opts) = @_;
+    if (defined($opts{remove})) {
+	@{$self->{options}} = grep { not &{$opts{remove}}($_) }
+				     @{$self->{options}};
+    }
+    if (defined($opts{keep})) {
+	@{$self->{options}} = grep { &{$opts{keep}}($_) }
+				     @{$self->{options}};
+    }
+}
+
+=item $string = $conf->output($fh)
+
+Write the options in the given filehandle (if defined) and return a string
+representation of the content (that would be) written.
+
+=item "$conf"
+
+Return a string representation of the content.
+
+=item $conf->save($file)
+
+Save the options in a file.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $ret = '';
+    foreach my $opt ($self->get_options()) {
+	$opt =~ s/^--//;
+	if ($opt =~ s/^([^=]+)=/$1 = "/) {
+	    $opt .= '"';
+	}
+	$opt .= "\n";
+	print { $fh } $opt if defined $fh;
+	$ret .= $opt;
+    }
+    return $ret;
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control.pm b/third_party/dpkg-dev/scripts/Dpkg/Control.pm
new file mode 100644
index 0000000..fae49d9
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control.pm
@@ -0,0 +1,200 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Types;
+use Dpkg::Control::Hash;
+use Dpkg::Control::Fields;
+
+use Exporter qw(import);
+
+use parent qw(Dpkg::Control::Hash);
+
+our @EXPORT = qw(CTRL_UNKNOWN CTRL_INFO_SRC CTRL_INFO_PKG CTRL_INDEX_SRC
+                 CTRL_INDEX_PKG CTRL_PKG_SRC CTRL_PKG_DEB CTRL_FILE_CHANGES
+                 CTRL_FILE_VENDOR CTRL_FILE_STATUS CTRL_CHANGELOG);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control - parse and manipulate official control-like information
+
+=head1 DESCRIPTION
+
+The Dpkg::Control object is a smart version of Dpkg::Control::Hash.
+It associates a type to the control information. That type can be
+used to know what fields are allowed and in what order they must be
+output.
+
+The types are constants that are exported by default. Here's the full
+list:
+
+=over 4
+
+=item CTRL_UNKNOWN
+
+This type is the default type, it indicates that the type of control
+information is not yet known.
+
+=item CTRL_INFO_SRC
+
+Corresponds to the first block of information in a F<debian/control> file in
+a Debian source package.
+
+=item CTRL_INFO_PKG
+
+Corresponds to subsequent blocks of information in a F<debian/control> file
+in a Debian source package.
+
+=item CTRL_INDEX_SRC
+
+Corresponds to an entry in a F<Sources> file of a source package
+repository.
+
+=item CTRL_INDEX_PKG
+
+Corresponds to an entry in a F<Packages> file of a binary package
+repository.
+
+=item CTRL_PKG_SRC
+
+Corresponds to a .dsc file of a Debian source package.
+
+=item CTRL_PKG_DEB
+
+Corresponds to the F<control> file generated by dpkg-gencontrol
+(F<DEBIAN/control>) and to the same file inside .deb packages.
+
+=item CTRL_FILE_CHANGES
+
+Corresponds to a .changes file.
+
+=item CTRL_FILE_VENDOR
+
+Corresponds to a vendor file in $Dpkg::CONFDIR/origins/.
+
+=item CTRL_FILE_STATUS
+
+Corresponds to an entry in dpkg's F<status> file ($Dpkg::ADMINDIR/status).
+
+=item CTRL_CHANGELOG
+
+Corresponds to the output of dpkg-parsechangelog.
+
+=back
+
+=head1 FUNCTIONS
+
+All the methods of Dpkg::Control::Hash are available. Those listed below
+are either new or overridden with a different behaviour.
+
+=over 4
+
+=item my $c = Dpkg::Control->new(%opts)
+
+If the "type" option is given, it's used to setup default values
+for other options. See set_options() for more details.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = Dpkg::Control::Hash->new();
+    bless $self, $class;
+    $self->set_options(%opts);
+
+    return $self;
+}
+
+=item $c->set_options(%opts)
+
+Changes the value of one or more options. If the "type" option is changed,
+it is used first to define default values for others options. The option
+"allow_pgp" is set to 1 for CTRL_PKG_SRC and CTRL_FILE_CHANGES and to 0
+otherwise. The option "drop_empty" is set to 0 for CTRL_INFO_PKG and
+CTRL_INFO_SRC and to 1 otherwise. The option "name" is set to a textual
+description of the type of control information.
+
+The output order is also set to match the ordered list returned by
+Dpkg::Control::Fields::field_ordered_list($type).
+
+=cut
+
+sub set_options {
+    my ($self, %opts) = @_;
+    if (exists $opts{type}) {
+        my $t = $opts{type};
+        $$self->{allow_pgp} = ($t & (CTRL_PKG_SRC | CTRL_FILE_CHANGES)) ? 1 : 0;
+        $$self->{drop_empty} = ($t & (CTRL_INFO_PKG | CTRL_INFO_SRC)) ?  0 : 1;
+        if ($t == CTRL_INFO_SRC) {
+            $$self->{name} = _g('general section of control info file');
+        } elsif ($t == CTRL_INFO_PKG) {
+            $$self->{name} = _g("package's section of control info file");
+        } elsif ($t == CTRL_CHANGELOG) {
+            $$self->{name} = _g('parsed version of changelog');
+        } elsif ($t == CTRL_INDEX_SRC) {
+            $$self->{name} = sprintf(_g("entry in repository's %s file"), 'Sources');
+        } elsif ($t == CTRL_INDEX_PKG) {
+            $$self->{name} = sprintf(_g("entry in repository's %s file"), 'Packages');
+        } elsif ($t == CTRL_PKG_SRC) {
+            $$self->{name} = sprintf(_g('%s file'), '.dsc');
+        } elsif ($t == CTRL_PKG_DEB) {
+            $$self->{name} = _g('control info of a .deb package');
+        } elsif ($t == CTRL_FILE_CHANGES) {
+            $$self->{name} = sprintf(_g('%s file'), '.changes');
+        } elsif ($t == CTRL_FILE_VENDOR) {
+            $$self->{name} = _g('vendor file');
+        } elsif ($t == CTRL_FILE_STATUS) {
+            $$self->{name} = _g("entry in dpkg's status file");
+        }
+        $self->set_output_order(field_ordered_list($opts{type}));
+    }
+
+    # Options set by the user override default values
+    $$self->{$_} = $opts{$_} foreach keys %opts;
+}
+
+=item $c->get_type()
+
+Returns the type of control information stored. See the type parameter
+set during new().
+
+=cut
+
+sub get_type {
+    my ($self) = @_;
+    return $$self->{type};
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/Changelog.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/Changelog.pm
new file mode 100644
index 0000000..a6890ea
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/Changelog.pm
@@ -0,0 +1,62 @@
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::Changelog;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Control;
+use parent qw(Dpkg::Control);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::Changelog - represent info fields output by dpkg-parsechangelog
+
+=head1 DESCRIPTION
+
+This object derives directly from Dpkg::Control with the type
+CTRL_CHANGELOG.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item $c = Dpkg::Control::Changelog->new()
+
+Create a new empty set of changelog related fields.
+
+=cut
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = Dpkg::Control->new(type => CTRL_CHANGELOG, @_);
+    return bless $self, $class;
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/Fields.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/Fields.pm
new file mode 100644
index 0000000..bc175462
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/Fields.pm
@@ -0,0 +1,65 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::Fields;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Carp;
+use Exporter qw(import);
+
+use Dpkg::Control::FieldsCore;
+use Dpkg::Vendor qw(run_vendor_hook);
+
+our @EXPORT = @Dpkg::Control::FieldsCore::EXPORT;
+
+# Register vendor specifics fields
+foreach my $op (run_vendor_hook('register-custom-fields')) {
+    next if not (defined $op and ref $op); # Skip when not implemented by vendor
+    my $func = shift @$op;
+    if ($func eq 'register') {
+        &field_register(@$op);
+    } elsif ($func eq 'insert_before') {
+        &field_insert_before(@$op);
+    } elsif ($func eq 'insert_after') {
+        &field_insert_after(@$op);
+    } else {
+        croak "vendor hook register-custom-fields sent bad data: @$op";
+    }
+}
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::Fields - manage (list of official) control fields
+
+=head1 DESCRIPTION
+
+The module contains a list of vendor-neutral and vendor-specific fieldnames
+with associated meta-data explaining in which type of control information
+they are allowed. The vendor-neutral fieldnames and all functions are
+inherited from Dpkg::Control::FieldsCore.
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/FieldsCore.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/FieldsCore.pm
new file mode 100644
index 0000000..1ae32086
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/FieldsCore.pm
@@ -0,0 +1,660 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::FieldsCore;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Exporter qw(import);
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Types;
+use Dpkg::Checksums;
+
+our @EXPORT = qw(field_capitalize field_is_official field_is_allowed_in
+                 field_transfer_single field_transfer_all
+                 field_list_src_dep field_list_pkg_dep field_get_dep_type
+                 field_get_sep_type
+                 field_ordered_list field_register
+                 field_insert_after field_insert_before
+                 FIELD_SEP_UNKNOWN FIELD_SEP_SPACE FIELD_SEP_COMMA
+                 FIELD_SEP_LINE);
+
+use constant {
+    ALL_PKG => CTRL_INFO_PKG | CTRL_INDEX_PKG | CTRL_PKG_DEB | CTRL_FILE_STATUS,
+    ALL_SRC => CTRL_INFO_SRC | CTRL_INDEX_SRC | CTRL_PKG_SRC,
+    ALL_CHANGES => CTRL_FILE_CHANGES | CTRL_CHANGELOG,
+};
+
+use constant {
+    FIELD_SEP_UNKNOWN => 0,
+    FIELD_SEP_SPACE => 1,
+    FIELD_SEP_COMMA => 2,
+    FIELD_SEP_LINE => 4,
+};
+
+# The canonical list of fields
+
+# Note that fields used only in dpkg's available file are not listed
+# Deprecated fields of dpkg's status file are also not listed
+our %FIELDS = (
+    'Architecture' => {
+        allowed => (ALL_PKG | ALL_SRC | CTRL_FILE_CHANGES) & (~CTRL_INFO_SRC),
+        separator => FIELD_SEP_SPACE,
+    },
+    'Binary' => {
+        allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES,
+        # XXX: This field values are separated either by space or comma
+        # depending on the context.
+        separator => FIELD_SEP_SPACE | FIELD_SEP_COMMA,
+    },
+    'Binary-Only' => {
+        allowed => ALL_CHANGES,
+    },
+    'Breaks' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 7,
+    },
+    'Bugs' => {
+        allowed => (ALL_PKG | CTRL_INFO_SRC | CTRL_FILE_VENDOR) & (~CTRL_INFO_PKG),
+    },
+    'Build-Conflicts' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 4,
+    },
+    'Build-Conflicts-Arch' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 5,
+    },
+    'Build-Conflicts-Indep' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 6,
+    },
+    'Build-Depends' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 1,
+    },
+    'Build-Depends-Arch' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 2,
+    },
+    'Build-Depends-Indep' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 3,
+    },
+    'Built-For-Profiles' => {
+        allowed => ALL_PKG | CTRL_FILE_CHANGES,
+        separator => FIELD_SEP_SPACE,
+    },
+    'Built-Using' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 10,
+    },
+    'Changed-By' => {
+        allowed => CTRL_FILE_CHANGES,
+    },
+    'Changes' => {
+        allowed => ALL_CHANGES,
+    },
+    'Closes' => {
+        allowed => ALL_CHANGES,
+        separator => FIELD_SEP_SPACE,
+    },
+    'Conffiles' => {
+        allowed => CTRL_FILE_STATUS,
+        separator => FIELD_SEP_LINE | FIELD_SEP_SPACE,
+    },
+    'Config-Version' => {
+        allowed => CTRL_FILE_STATUS,
+    },
+    'Conflicts' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 6,
+    },
+    'Date' => {
+        allowed => ALL_CHANGES,
+    },
+    'Depends' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 2,
+    },
+    'Description' => {
+        allowed => ALL_PKG | CTRL_FILE_CHANGES,
+    },
+    'Directory' => {
+        allowed => CTRL_INDEX_SRC,
+    },
+    'Distribution' => {
+        allowed => ALL_CHANGES,
+    },
+    'Enhances' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 5,
+    },
+    'Essential' => {
+        allowed => ALL_PKG,
+    },
+    'Filename' => {
+        allowed => CTRL_INDEX_PKG,
+        separator => FIELD_SEP_LINE | FIELD_SEP_SPACE,
+    },
+    'Files' => {
+        allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES,
+        separator => FIELD_SEP_LINE | FIELD_SEP_SPACE,
+    },
+    'Format' => {
+        allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES,
+    },
+    'Homepage' => {
+        allowed => ALL_SRC | ALL_PKG,
+    },
+    'Installed-Size' => {
+        allowed => ALL_PKG & ~CTRL_INFO_PKG,
+    },
+    'Installer-Menu-Item' => {
+        allowed => ALL_PKG,
+    },
+    'Kernel-Version' => {
+        allowed => ALL_PKG,
+    },
+    'Origin' => {
+        allowed => (ALL_PKG | ALL_SRC) & (~CTRL_INFO_PKG),
+    },
+    'Maintainer' => {
+        allowed => CTRL_PKG_DEB | ALL_SRC | ALL_CHANGES,
+    },
+    'Multi-Arch' => {
+        allowed => ALL_PKG,
+    },
+    'Package' => {
+        allowed => ALL_PKG,
+    },
+    'Package-List' => {
+        allowed => ALL_SRC & ~CTRL_INFO_SRC,
+        separator => FIELD_SEP_LINE | FIELD_SEP_SPACE,
+    },
+    'Package-Type' => {
+        allowed => ALL_PKG,
+    },
+    'Parent' => {
+        allowed => CTRL_FILE_VENDOR,
+    },
+    'Pre-Depends' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 1,
+    },
+    'Priority' => {
+        allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG,
+    },
+    'Provides' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 9,
+    },
+    'Recommends' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 3,
+    },
+    'Replaces' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'union',
+        dep_order => 8,
+    },
+    'Section' => {
+        allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG,
+    },
+    'Size' => {
+        allowed => CTRL_INDEX_PKG,
+        separator => FIELD_SEP_LINE | FIELD_SEP_SPACE,
+    },
+    'Source' => {
+        allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) &
+                   (~(CTRL_INDEX_SRC | CTRL_INFO_PKG)),
+    },
+    'Standards-Version' => {
+        allowed => ALL_SRC,
+    },
+    'Status' => {
+        allowed => CTRL_FILE_STATUS,
+        separator => FIELD_SEP_SPACE,
+    },
+    'Subarchitecture' => {
+        allowed => ALL_PKG,
+    },
+    'Suggests' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+        dependency => 'normal',
+        dep_order => 4,
+    },
+    'Tag' => {
+        allowed => ALL_PKG,
+        separator => FIELD_SEP_COMMA,
+    },
+    'Task' => {
+        allowed => ALL_PKG,
+    },
+    'Triggers-Awaited' => {
+        allowed => CTRL_FILE_STATUS,
+        separator => FIELD_SEP_SPACE,
+    },
+    'Triggers-Pending' => {
+        allowed => CTRL_FILE_STATUS,
+        separator => FIELD_SEP_SPACE,
+    },
+    'Uploaders' => {
+        allowed => ALL_SRC,
+        separator => FIELD_SEP_COMMA,
+    },
+    'Urgency' => {
+        allowed => ALL_CHANGES,
+    },
+    'Vcs-Browser' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Arch' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Bzr' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Cvs' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Darcs' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Git' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Hg' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Mtn' => {
+        allowed => ALL_SRC,
+    },
+    'Vcs-Svn' => {
+        allowed => ALL_SRC,
+    },
+    'Vendor' => {
+        allowed => CTRL_FILE_VENDOR,
+    },
+    'Vendor-Url' => {
+        allowed => CTRL_FILE_VENDOR,
+    },
+    'Version' => {
+        allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) &
+                    (~(CTRL_INFO_SRC | CTRL_INFO_PKG)),
+    },
+);
+
+my @checksum_fields = map { &field_capitalize("Checksums-$_") } checksums_get_list();
+my @sum_fields = map { $_ eq 'md5' ? 'MD5sum' : &field_capitalize($_) }
+                 checksums_get_list();
+&field_register($_, CTRL_PKG_SRC | CTRL_FILE_CHANGES) foreach @checksum_fields;
+&field_register($_, CTRL_INDEX_PKG,
+                separator => FIELD_SEP_LINE | FIELD_SEP_SPACE) foreach @sum_fields;
+
+our %FIELD_ORDER = (
+    CTRL_PKG_DEB() => [
+        qw(Package Package-Type Source Version Built-Using Kernel-Version
+        Built-For-Profiles Architecture Subarchitecture
+        Installer-Menu-Item Essential Origin Bugs
+        Maintainer Installed-Size), &field_list_pkg_dep(),
+        qw(Section Priority Multi-Arch Homepage Description Tag Task)
+    ],
+    CTRL_PKG_SRC() => [
+        qw(Format Source Binary Architecture Version Origin Maintainer
+        Uploaders Homepage Standards-Version Vcs-Browser
+        Vcs-Arch Vcs-Bzr Vcs-Cvs Vcs-Darcs Vcs-Git Vcs-Hg Vcs-Mtn
+        Vcs-Svn), &field_list_src_dep(), qw(Package-List),
+        @checksum_fields, qw(Files)
+    ],
+    CTRL_FILE_CHANGES() => [
+        qw(Format Date Source Binary Binary-Only Built-For-Profiles Architecture
+        Version Distribution Urgency Maintainer Changed-By Description
+        Closes Changes),
+        @checksum_fields, qw(Files)
+    ],
+    CTRL_CHANGELOG() => [
+        qw(Source Binary-Only Version Distribution Urgency Maintainer
+        Date Closes Changes)
+    ],
+    CTRL_FILE_STATUS() => [ # Same as fieldinfos in lib/dpkg/parse.c
+        qw(Package Essential Status Priority Section Installed-Size Origin
+        Maintainer Bugs Architecture Multi-Arch Source Version Config-Version
+        Replaces Provides Depends Pre-Depends Recommends Suggests Breaks
+        Conflicts Enhances Conffiles Description Triggers-Pending
+        Triggers-Awaited)
+    ],
+);
+# Order for CTRL_INDEX_PKG is derived from CTRL_PKG_DEB
+$FIELD_ORDER{CTRL_INDEX_PKG()} = [ @{$FIELD_ORDER{CTRL_PKG_DEB()}} ];
+&field_insert_before(CTRL_INDEX_PKG, 'Section', 'Filename', 'Size', @sum_fields);
+# Order for CTRL_INDEX_SRC is derived from CTRL_PKG_SRC
+$FIELD_ORDER{CTRL_INDEX_SRC()} = [ @{$FIELD_ORDER{CTRL_PKG_SRC()}} ];
+@{$FIELD_ORDER{CTRL_INDEX_SRC()}} = map { $_ eq 'Source' ? 'Package' : $_ }
+                                  @{$FIELD_ORDER{CTRL_PKG_SRC()}};
+&field_insert_after(CTRL_INDEX_SRC, 'Version', 'Priority', 'Section');
+&field_insert_before(CTRL_INDEX_SRC, 'Checksums-Md5', 'Directory');
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::FieldsCore - manage (list of official) control fields
+
+=head1 DESCRIPTION
+
+The modules contains a list of fieldnames with associated meta-data explaining
+in which type of control information they are allowed. The types are the
+CTRL_* constants exported by Dpkg::Control.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $f = field_capitalize($field_name)
+
+Returns the field name properly capitalized. All characters are lowercase,
+except the first of each word (words are separated by a hyphen in field names).
+
+=cut
+
+sub field_capitalize($) {
+    my $field = lc(shift);
+    # Some special cases due to history
+    return 'MD5sum' if $field eq 'md5sum';
+    return uc($field) if checksums_is_supported($field);
+    # Generic case
+    return join '-', map { ucfirst } split /-/, $field;
+}
+
+=item field_is_official($fname)
+
+Returns true if the field is official and known.
+
+=cut
+
+sub field_is_official($) {
+    return exists $FIELDS{field_capitalize($_[0])};
+}
+
+=item field_is_allowed_in($fname, @types)
+
+Returns true (1) if the field $fname is allowed in all the types listed in
+the list. Note that you can use type sets instead of individual types (ex:
+CTRL_FILE_CHANGES | CTRL_CHANGELOG).
+
+field_allowed_in(A|B, C) returns true only if the field is allowed in C
+and either A or B.
+
+Undef is returned for non-official fields.
+
+=cut
+
+sub field_is_allowed_in($@) {
+    my ($field, @types) = @_;
+    $field = field_capitalize($field);
+    return unless field_is_official($field);
+
+    return 0 if not scalar(@types);
+    foreach my $type (@types) {
+        next if $type == CTRL_UNKNOWN; # Always allowed
+        return 0 unless $FIELDS{$field}{allowed} & $type;
+    }
+    return 1;
+}
+
+=item field_transfer_single($from, $to, $field)
+
+If appropriate, copy the value of the field named $field taken from the
+$from Dpkg::Control object to the $to Dpkg::Control object.
+
+Official fields are copied only if the field is allowed in both types of
+objects. Custom fields are treated in a specific manner. When the target
+is not among CTRL_PKG_SRC, CTRL_PKG_DEB or CTRL_FILE_CHANGES, then they
+are alway copied as is (the X- prefix is kept). Otherwise they are not
+copied except if the target object matches the target destination encoded
+in the field name. The initial X denoting custom fields can be followed by
+one or more letters among "S" (Source: corresponds to CTRL_PKG_SRC), "B"
+(Binary: corresponds to CTRL_PKG_DEB) or "C" (Changes: corresponds to
+CTRL_FILE_CHANGES).
+
+Returns undef if nothing has been copied or the name of the new field
+added to $to otherwise.
+
+=cut
+
+sub field_transfer_single($$;$) {
+    my ($from, $to, $field) = @_;
+    $field //= $_;
+    my ($from_type, $to_type) = ($from->get_type(), $to->get_type());
+    $field = field_capitalize($field);
+
+    if (field_is_allowed_in($field, $from_type, $to_type)) {
+        $to->{$field} = $from->{$field};
+        return $field;
+    } elsif ($field =~ /^X([SBC]*)-/i) {
+        my $dest = $1;
+        if (($dest =~ /B/i and $to_type == CTRL_PKG_DEB) or
+            ($dest =~ /S/i and $to_type == CTRL_PKG_SRC) or
+            ($dest =~ /C/i and $to_type == CTRL_FILE_CHANGES))
+        {
+            my $new = $field;
+            $new =~ s/^X([SBC]*)-//i;
+            $to->{$new} = $from->{$field};
+            return $new;
+        } elsif ($to_type != CTRL_PKG_DEB and
+		 $to_type != CTRL_PKG_SRC and
+		 $to_type != CTRL_FILE_CHANGES)
+	{
+	    $to->{$field} = $from->{$field};
+	    return $field;
+	}
+    } elsif (not field_is_allowed_in($field, $from_type)) {
+        warning(_g("unknown information field '%s' in input data in %s"),
+                $field, $from->get_option('name') || _g('control information'));
+    }
+    return;
+}
+
+=item field_transfer_all($from, $to)
+
+Transfer all appropriate fields from $from to $to. Calls
+field_transfer_single() on all fields available in $from.
+
+Returns the list of fields that have been added to $to.
+
+=cut
+
+sub field_transfer_all($$) {
+    my ($from, $to) = @_;
+    my (@res, $res);
+    foreach my $k (keys %$from) {
+        $res = field_transfer_single($from, $to, $k);
+        push @res, $res if $res and defined wantarray;
+    }
+    return @res;
+}
+
+=item field_ordered_list($type)
+
+Returns an ordered list of fields for a given type of control information.
+This list can be used to output the fields in a predictable order.
+The list might be empty for types where the order does not matter much.
+
+=cut
+
+sub field_ordered_list($) {
+    my ($type) = @_;
+    return @{$FIELD_ORDER{$type}} if exists $FIELD_ORDER{$type};
+    return ();
+}
+
+=item field_list_src_dep()
+
+List of fields that contains dependencies-like information in a source
+Debian package.
+
+=cut
+
+sub field_list_src_dep() {
+    my @list = sort {
+        $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order}
+    } grep {
+        field_is_allowed_in($_, CTRL_PKG_SRC) and
+        exists $FIELDS{$_}{dependency}
+    } keys %FIELDS;
+    return @list;
+}
+
+=item field_list_pkg_dep()
+
+List of fields that contains dependencies-like information in a binary
+Debian package. The fields that express real dependencies are sorted from
+the stronger to the weaker.
+
+=cut
+
+sub field_list_pkg_dep() {
+    my @keys = keys %FIELDS;
+    my @list = sort {
+        $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order}
+    } grep {
+        field_is_allowed_in($_, CTRL_PKG_DEB) and
+        exists $FIELDS{$_}{dependency}
+    } @keys;
+    return @list;
+}
+
+=item field_get_dep_type($field)
+
+Return the type of the dependency expressed by the given field. Can
+either be "normal" for a real dependency field (Pre-Depends, Depends, ...)
+or "union" for other relation fields sharing the same syntax (Conflicts,
+Breaks, ...). Returns undef for fields which are not dependencies.
+
+=cut
+
+sub field_get_dep_type($) {
+    my $field = field_capitalize($_[0]);
+    return unless field_is_official($field);
+    return $FIELDS{$field}{dependency} if exists $FIELDS{$field}{dependency};
+    return;
+}
+
+=item field_get_sep_type($field)
+
+Return the type of the field value separator. Can be one of FIELD_SEP_UNKNOWN,
+FIELD_SEP_SPACE, FIELD_SEP_COMMA or FIELD_SEP_LINE.
+
+=cut
+
+sub field_get_sep_type($) {
+    my $field = field_capitalize($_[0]);
+
+    return $FIELDS{$field}{separator} if exists $FIELDS{$field}{separator};
+    return FIELD_SEP_UNKNOWN;
+}
+
+=item field_register($field, $allowed_types, %opts)
+
+Register a new field as being allowed in control information of specified
+types. %opts is optional
+
+=cut
+
+sub field_register($$;@) {
+    my ($field, $types, %opts) = @_;
+    $field = field_capitalize($field);
+    $FIELDS{$field} = {
+        allowed => $types,
+        %opts
+    };
+}
+
+=item field_insert_after($type, $ref, @fields)
+
+Place field after another one ($ref) in output of control information of
+type $type.
+
+=cut
+sub field_insert_after($$@) {
+    my ($type, $field, @fields) = @_;
+    return 0 if not exists $FIELD_ORDER{$type};
+    ($field, @fields) = map { field_capitalize($_) } ($field, @fields);
+    @{$FIELD_ORDER{$type}} = map {
+        ($_ eq $field) ? ($_, @fields) : $_
+    } @{$FIELD_ORDER{$type}};
+    return 1;
+}
+
+=item field_insert_before($type, $ref, @fields)
+
+Place field before another one ($ref) in output of control information of
+type $type.
+
+=cut
+sub field_insert_before($$@) {
+    my ($type, $field, @fields) = @_;
+    return 0 if not exists $FIELD_ORDER{$type};
+    ($field, @fields) = map { field_capitalize($_) } ($field, @fields);
+    @{$FIELD_ORDER{$type}} = map {
+        ($_ eq $field) ? (@fields, $_) : $_
+    } @{$FIELD_ORDER{$type}};
+    return 1;
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/Hash.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/Hash.pm
new file mode 100644
index 0000000..85eccac
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/Hash.pm
@@ -0,0 +1,46 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::Hash;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Fields; # Force execution of vendor hook.
+
+use parent qw(Dpkg::Control::HashCore);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::Hash - parse and manipulate a block of RFC822-like fields
+
+=head1 DESCRIPTION
+
+This module is just like Dpkg::Control::HashCore, with vendor-specific
+field knowledge.
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/HashCore.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/HashCore.pm
new file mode 100644
index 0000000..e2ceb66
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/HashCore.pm
@@ -0,0 +1,540 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::HashCore;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::FieldsCore;
+
+# This module cannot use Dpkg::Control::Fields, because that one makes use
+# of Dpkg::Vendor which at the same time uses this module, which would turn
+# into a compilation error. We can use Dpkg::Control::FieldsCore instead.
+
+use parent qw(Dpkg::Interface::Storable);
+
+use overload
+    '%{}' => sub { ${$_[0]}->{fields} },
+    'eq' => sub { "$_[0]" eq "$_[1]" };
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::HashCore - parse and manipulate a block of RFC822-like fields
+
+=head1 DESCRIPTION
+
+The Dpkg::Control::Hash object is a hash-like representation of a set of
+RFC822-like fields. The fields names are case insensitive and are always
+capitalized the same when output (see field_capitalize function in
+Dpkg::Control::Fields).
+The order in which fields have been set is remembered and is used
+to be able to dump back the same content. The output order can also be
+overridden if needed.
+
+You can store arbitrary values in the hash, they will always be properly
+escaped in the output to conform to the syntax of control files. This is
+relevant mainly for multilines values: while the first line is always output
+unchanged directly after the field name, supplementary lines are
+modified. Empty lines and lines containing only dots are prefixed with
+" ." (space + dot) while other lines are prefixed with a single space.
+
+During parsing, trailing spaces are stripped on all lines while leading
+spaces are stripped only on the first line of each field.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $c = Dpkg::Control::Hash->new(%opts)
+
+Creates a new object with the indicated options. Supported options
+are:
+
+=over 8
+
+=item allow_pgp
+
+Configures the parser to accept PGP signatures around the control
+information. Value can be 0 (default) or 1.
+
+=item allow_duplicate
+
+Configures the parser to allow duplicate fields in the control
+information. Value can be 0 (default) or 1.
+
+=item drop_empty
+
+Defines if empty fields are dropped during the output. Value can be 0
+(default) or 1.
+
+=item name
+
+The user friendly name of the information stored in the object. It might
+be used in some error messages or warnings. A default name might be set
+depending on the type.
+
+=back
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    # Object is a scalar reference and not a hash ref to avoid
+    # infinite recursion due to overloading hash-derefencing
+    my $self = \{
+        in_order => [],
+        out_order => [],
+        is_pgp_signed => 0,
+        allow_pgp => 0,
+        allow_duplicate => 0,
+        drop_empty => 0,
+    };
+    bless $self, $class;
+
+    $$self->{fields} = Dpkg::Control::HashCore::Tie->new($self);
+
+    # Options set by the user override default values
+    $$self->{$_} = $opts{$_} foreach keys %opts;
+
+    return $self;
+}
+
+# There is naturally a circular reference between the tied hash and its
+# containing object. Happily, the extra layer of scalar reference can
+# be used to detect the destruction of the object and break the loop so
+# that everything gets garbage-collected.
+
+sub DESTROY {
+    my ($self) = @_;
+    delete $$self->{fields};
+}
+
+=item $c->set_options($option, %opts)
+
+Changes the value of one or more options.
+
+=cut
+
+sub set_options {
+    my ($self, %opts) = @_;
+    $$self->{$_} = $opts{$_} foreach keys %opts;
+}
+
+=item my $value = $c->get_option($option)
+
+Returns the value of the corresponding option.
+
+=cut
+
+sub get_option {
+    my ($self, $k) = @_;
+    return $$self->{$k};
+}
+
+=item $c->load($file)
+
+Parse the content of $file. Exits in case of errors. Returns true if some
+fields have been parsed.
+
+=item $c->parse_error($file, $fmt, ...)
+
+Prints an error message and dies on syntax parse errors.
+
+=cut
+
+sub parse_error {
+    my ($self, $file, $msg) = (shift, shift, shift);
+
+    $msg = sprintf($msg, @_) if (@_);
+    error(_g('syntax error in %s at line %d: %s'), $file, $., $msg);
+}
+
+=item $c->parse($fh, $description)
+
+Parse a control file from the given filehandle. Exits in case of errors.
+$description is used to describe the filehandle, ideally it's a filename
+or a description of where the data comes from. It's used in error
+messages. Returns true if some fields have been parsed.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $desc) = @_;
+
+    my $paraborder = 1;
+    my $parabody = 0;
+    my $cf; # Current field
+    my $expect_pgp_sig = 0;
+
+    while (<$fh>) {
+	chomp;
+	next if m/^\s*$/ and $paraborder;
+	next if (m/^#/);
+	$paraborder = 0;
+	if (m/^(\S+?)\s*:\s*(.*)$/) {
+	    $parabody = 1;
+	    if ($1 =~ m/^-/) {
+		$self->parse_error($desc, _g('field cannot start with a hyphen'));
+	    }
+	    my ($name, $value) = ($1, $2);
+	    if (exists $self->{$name}) {
+		unless ($$self->{allow_duplicate}) {
+		    $self->parse_error($desc, _g('duplicate field %s found'), $name);
+		}
+	    }
+	    $value =~ s/\s*$//;
+	    $self->{$name} = $value;
+	    $cf = $name;
+	} elsif (m/^\s(\s*\S.*)$/) {
+	    my $line = $1;
+	    unless (defined($cf)) {
+		$self->parse_error($desc, _g('continued value line not in field'));
+            }
+	    if ($line =~ /^\.+$/) {
+		$line = substr $line, 1;
+	    }
+	    $line =~ s/\s*$//;
+	    $self->{$cf} .= "\n$line";
+	} elsif (m/^-----BEGIN PGP SIGNED MESSAGE-----[\r\t ]*$/) {
+	    $expect_pgp_sig = 1;
+	    if ($$self->{allow_pgp} and not $parabody) {
+		# Skip PGP headers
+		while (<$fh>) {
+		    last if m/^\s*$/;
+		}
+	    } else {
+		$self->parse_error($desc, _g('PGP signature not allowed here'));
+	    }
+	} elsif (m/^\s*$/ ||
+	         ($expect_pgp_sig && m/^-----BEGIN PGP SIGNATURE-----[\r\t ]*$/)) {
+	    if ($expect_pgp_sig) {
+		# Skip empty lines
+		$_ = <$fh> while defined($_) && $_ =~ /^\s*$/;
+		unless (length $_) {
+		    $self->parse_error($desc, _g('expected PGP signature, ' .
+		                                 'found EOF after blank line'));
+		}
+		chomp;
+		unless (m/^-----BEGIN PGP SIGNATURE-----[\r\t ]*$/) {
+		    $self->parse_error($desc, _g('expected PGP signature, ' .
+		                                 "found something else \`%s'"), $_);
+                }
+		# Skip PGP signature
+		while (<$fh>) {
+		    chomp;
+		    last if m/^-----END PGP SIGNATURE-----[\r\t ]*$/;
+		}
+		unless (defined($_)) {
+		    $self->parse_error($desc, _g('unfinished PGP signature'));
+                }
+		# This does not mean the signature is correct, that needs to
+		# be verified by gnupg.
+		$$self->{is_pgp_signed} = 1;
+	    }
+	    last; # Finished parsing one block
+	} else {
+	    $self->parse_error($desc,
+	                       _g('line with unknown format (not field-colon-value)'));
+	}
+    }
+
+    if ($expect_pgp_sig and not $$self->{is_pgp_signed}) {
+	$self->parse_error($desc, _g('unfinished PGP signature'));
+    }
+
+    return defined($cf);
+}
+
+=item $c->find_custom_field($name)
+
+Scan the fields and look for a user specific field whose name matches the
+following regex: /X[SBC]*-$name/i. Return the name of the field found or
+undef if nothing has been found.
+
+=cut
+
+sub find_custom_field {
+    my ($self, $name) = @_;
+    foreach my $key (keys %$self) {
+        return $key if $key =~ /^X[SBC]*-\Q$name\E$/i;
+    }
+    return;
+}
+
+=item $c->get_custom_field($name)
+
+Identify a user field and retrieve its value.
+
+=cut
+
+sub get_custom_field {
+    my ($self, $name) = @_;
+    my $key = $self->find_custom_field($name);
+    return $self->{$key} if defined $key;
+    return;
+}
+
+=item $c->save($filename)
+
+Write the string representation of the control information to a
+file.
+
+=item my $str = $c->output()
+
+=item "$c"
+
+Get a string representation of the control information. The fields
+are sorted in the order in which they have been read or set except
+if the order has been overridden with set_output_order().
+
+=item $c->output($fh)
+
+Print the string representation of the control information to a
+filehandle.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str = '';
+    my @keys;
+    if (@{$$self->{out_order}}) {
+        my $i = 1;
+        my $imp = {};
+        $imp->{$_} = $i++ foreach @{$$self->{out_order}};
+        @keys = sort {
+            if (defined $imp->{$a} && defined $imp->{$b}) {
+                $imp->{$a} <=> $imp->{$b};
+            } elsif (defined($imp->{$a})) {
+                -1;
+            } elsif (defined($imp->{$b})) {
+                1;
+            } else {
+                $a cmp $b;
+            }
+        } keys %$self;
+    } else {
+        @keys = @{$$self->{in_order}};
+    }
+
+    foreach my $key (@keys) {
+	if (exists $self->{$key}) {
+	    my $value = $self->{$key};
+            # Skip whitespace-only fields
+            next if $$self->{drop_empty} and $value !~ m/\S/;
+	    # Escape data to follow control file syntax
+	    my @lines = split(/\n/, $value);
+	    $value = (scalar @lines) ? shift @lines : '';
+	    foreach (@lines) {
+		s/\s+$//;
+		if (/^$/ or /^\.+$/) {
+		    $value .= "\n .$_";
+		} else {
+		    $value .= "\n $_";
+		}
+	    }
+	    # Print it out
+            if ($fh) {
+	        print { $fh } "$key: $value\n"
+	            or syserr(_g('write error on control data'));
+            }
+	    $str .= "$key: $value\n" if defined wantarray;
+	}
+    }
+    return $str;
+}
+
+=item $c->set_output_order(@fields)
+
+Define the order in which fields will be displayed in the output() method.
+
+=cut
+
+sub set_output_order {
+    my ($self, @fields) = @_;
+
+    $$self->{out_order} = [@fields];
+}
+
+=item $c->apply_substvars($substvars)
+
+Update all fields by replacing the variables references with
+the corresponding value stored in the Dpkg::Substvars object.
+
+=cut
+
+sub apply_substvars {
+    my ($self, $substvars, %opts) = @_;
+
+    # Add substvars to refer to other fields
+    foreach my $f (keys %$self) {
+        $substvars->set_as_used("F:$f", $self->{$f});
+    }
+
+    foreach my $f (keys %$self) {
+        my $v = $substvars->substvars($self->{$f}, %opts);
+	if ($v ne $self->{$f}) {
+	    my $sep;
+
+	    $sep = field_get_sep_type($f);
+
+	    # If we replaced stuff, ensure we're not breaking
+	    # a dependency field by introducing empty lines, or multiple
+	    # commas
+
+	    if ($sep & (FIELD_SEP_COMMA | FIELD_SEP_LINE)) {
+	        # Drop empty/whitespace-only lines
+	        $v =~ s/\n[ \t]*(\n|$)/$1/;
+	    }
+
+	    if ($sep & FIELD_SEP_COMMA) {
+	        $v =~ s/,[\s,]*,/,/g;
+	        $v =~ s/^\s*,\s*//;
+	        $v =~ s/\s*,\s*$//;
+	    }
+	}
+        $v =~ s/\$\{\}/\$/g; # XXX: what for?
+
+        $self->{$f} = $v;
+    }
+}
+
+package Dpkg::Control::HashCore::Tie;
+
+# This object is used to tie a hash. It implements hash-like functions by
+# normalizing the name of fields received in keys (using
+# Dpkg::Control::Fields::field_capitalize). It also stores the order in
+# which fields have been added in order to be able to dump them in the
+# same order. But the order information is stored in a parent object of
+# type Dpkg::Control.
+
+use Dpkg::Checksums;
+use Dpkg::Control::FieldsCore;
+
+use Carp;
+use Tie::Hash;
+use parent -norequire, qw(Tie::ExtraHash);
+
+# $self->[0] is the real hash
+# $self->[1] is a reference to the hash contained by the parent object.
+# This reference bypasses the top-level scalar reference of a
+# Dpkg::Control::Hash, hence ensuring that that reference gets DESTROYed
+# properly.
+
+# Dpkg::Control::Hash->new($parent)
+#
+# Return a reference to a tied hash implementing storage of simple
+# "field: value" mapping as used in many Debian-specific files.
+
+sub new {
+    my $class = shift;
+    my $hash = {};
+    tie %{$hash}, $class, @_;
+    return $hash;
+}
+
+sub TIEHASH  {
+    my ($class, $parent) = @_;
+    croak 'parent object must be Dpkg::Control::Hash'
+        if not $parent->isa('Dpkg::Control::HashCore') and
+           not $parent->isa('Dpkg::Control::Hash');
+    return bless [ {}, $$parent ], $class;
+}
+
+sub FETCH {
+    my ($self, $key) = @_;
+    $key = lc($key);
+    return $self->[0]->{$key} if exists $self->[0]->{$key};
+    return;
+}
+
+sub STORE {
+    my ($self, $key, $value) = @_;
+    my $parent = $self->[1];
+    $key = lc($key);
+    if (not exists $self->[0]->{$key}) {
+	push @{$parent->{in_order}}, field_capitalize($key);
+    }
+    $self->[0]->{$key} = $value;
+}
+
+sub EXISTS {
+    my ($self, $key) = @_;
+    $key = lc($key);
+    return exists $self->[0]->{$key};
+}
+
+sub DELETE {
+    my ($self, $key) = @_;
+    my $parent = $self->[1];
+    my $in_order = $parent->{in_order};
+    $key = lc($key);
+    if (exists $self->[0]->{$key}) {
+	delete $self->[0]->{$key};
+	@$in_order = grep { lc($_) ne $key } @$in_order;
+	return 1;
+    } else {
+	return 0;
+    }
+}
+
+sub FIRSTKEY {
+    my $self = shift;
+    my $parent = $self->[1];
+    foreach (@{$parent->{in_order}}) {
+	return $_ if exists $self->[0]->{lc($_)};
+    }
+}
+
+sub NEXTKEY {
+    my ($self, $last) = @_;
+    my $parent = $self->[1];
+    my $found = 0;
+    foreach (@{$parent->{in_order}}) {
+	if ($found) {
+	    return $_ if exists $self->[0]->{lc($_)};
+	} else {
+	    $found = 1 if $_ eq $last;
+	}
+    }
+    return;
+}
+
+1;
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New method: parse_error().
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/Info.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/Info.pm
new file mode 100644
index 0000000..17c5b892
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/Info.pm
@@ -0,0 +1,209 @@
+# Copyright © 2007-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::Info;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Control;
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use parent qw(Dpkg::Interface::Storable);
+
+use overload
+    '@{}' => sub { return [ $_[0]->{source}, @{$_[0]->{packages}} ] };
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::Info - parse files like debian/control
+
+=head1 DESCRIPTION
+
+It provides an object to access data of files that follow the same
+syntax as F<debian/control>.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item $c = Dpkg::Control::Info->new($file)
+
+Create a new Dpkg::Control::Info object for $file. If $file is omitted, it
+loads F<debian/control>. If file is "-", it parses the standard input.
+
+=cut
+
+sub new {
+    my ($this, $arg) = @_;
+    my $class = ref($this) || $this;
+    my $self = {
+	source => undef,
+	packages => [],
+    };
+    bless $self, $class;
+    if ($arg) {
+	$self->load($arg);
+    } else {
+	$self->load('debian/control');
+    }
+    return $self;
+}
+
+=item $c->reset()
+
+Resets what got read.
+
+=cut
+
+sub reset {
+    my $self = shift;
+    $self->{source} = undef;
+    $self->{packages} = [];
+}
+
+=item $c->load($file)
+
+Load the content of $file. Exits in case of errors. If file is "-", it
+loads from the standard input.
+
+=item $c->parse($fh, $description)
+
+Parse a control file from the given filehandle. Exits in case of errors.
+$description is used to describe the filehandle, ideally it's a filename
+or a description of where the data comes from. It's used in error
+messages.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $desc) = @_;
+    $self->reset();
+    my $cdata = Dpkg::Control->new(type => CTRL_INFO_SRC);
+    return if not $cdata->parse($fh, $desc);
+    $self->{source} = $cdata;
+    unless (exists $cdata->{Source}) {
+	$cdata->parse_error($desc, _g('first block lacks a source field'));
+    }
+    while (1) {
+	$cdata = Dpkg::Control->new(type => CTRL_INFO_PKG);
+        last if not $cdata->parse($fh, $desc);
+	push @{$self->{packages}}, $cdata;
+	unless (exists $cdata->{Package}) {
+	    $cdata->parse_error($desc, _g("block lacks the '%s' field"),
+	                        'Package');
+	}
+	unless (exists $cdata->{Architecture}) {
+	    $cdata->parse_error($desc, _g("block lacks the '%s' field"),
+	                        'Architecture');
+	}
+
+    }
+}
+
+=item $c->[0]
+
+=item $c->get_source()
+
+Returns a Dpkg::Control object containing the fields concerning the
+source package.
+
+=cut
+
+sub get_source {
+    my $self = shift;
+    return $self->{source};
+}
+
+=item $c->get_pkg_by_idx($idx)
+
+Returns a Dpkg::Control object containing the fields concerning the binary
+package numbered $idx (starting at 1).
+
+=cut
+
+sub get_pkg_by_idx {
+    my ($self, $idx) = @_;
+    return $self->{packages}[--$idx];
+}
+
+=item $c->get_pkg_by_name($name)
+
+Returns a Dpkg::Control object containing the fields concerning the binary
+package named $name.
+
+=cut
+
+sub get_pkg_by_name {
+    my ($self, $name) = @_;
+    foreach my $pkg (@{$self->{packages}}) {
+	return $pkg if ($pkg->{Package} eq $name);
+    }
+    return;
+}
+
+
+=item $c->get_packages()
+
+Returns a list containing the Dpkg::Control objects for all binary packages.
+
+=cut
+
+sub get_packages {
+    my $self = shift;
+    return @{$self->{packages}};
+}
+
+=item $c->output($filehandle)
+
+Dump the content into a filehandle.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str;
+    $str .= $self->{source}->output($fh);
+    foreach my $pkg (@{$self->{packages}}) {
+	print { $fh } "\n";
+	$str .= "\n" . $pkg->output($fh);
+    }
+    return $str;
+}
+
+=item "$c"
+
+Return a string representation of the content.
+
+=item @{$c}
+
+Return a list of Dpkg::Control objects, the first one is corresponding to
+source information and the following ones are the binary packages
+information.
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Control/Types.pm b/third_party/dpkg-dev/scripts/Dpkg/Control/Types.pm
new file mode 100644
index 0000000..d8bc0b3
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Control/Types.pm
@@ -0,0 +1,62 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Control::Types;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Exporter qw(import);
+our @EXPORT = qw(CTRL_UNKNOWN CTRL_INFO_SRC CTRL_INFO_PKG CTRL_INDEX_SRC
+                 CTRL_INDEX_PKG CTRL_PKG_SRC CTRL_PKG_DEB CTRL_FILE_CHANGES
+                 CTRL_FILE_VENDOR CTRL_FILE_STATUS CTRL_CHANGELOG);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Control::Types - export CTRL_* constants
+
+=head1 DESCRIPTION
+
+You should not use this module directly. Instead you more likely
+want to use Dpkg::Control which also re-exports the same constants.
+
+This module has been introduced solely to avoid a dependency loop
+between Dpkg::Control and Dpkg::Control::Fields.
+
+=cut
+
+use constant {
+    CTRL_UNKNOWN => 0,
+    CTRL_INFO_SRC => 1,      # First control block in debian/control
+    CTRL_INFO_PKG => 2,      # Subsequent control blocks in debian/control
+    CTRL_INDEX_SRC => 4,     # Entry in repository's Packages files
+    CTRL_INDEX_PKG => 8,     # Entry in repository's Sources files
+    CTRL_PKG_SRC => 16,      # .dsc file of source package
+    CTRL_PKG_DEB => 32,      # DEBIAN/control in binary packages
+    CTRL_FILE_CHANGES => 64, # .changes file
+    CTRL_FILE_VENDOR => 128, # File in $Dpkg::CONFDIR/origins
+    CTRL_FILE_STATUS => 256, # $Dpkg::ADMINDIR/status
+    CTRL_CHANGELOG => 512,   # Output of dpkg-parsechangelog
+};
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Deps.pm b/third_party/dpkg-dev/scripts/Dpkg/Deps.pm
new file mode 100644
index 0000000..7bc18153
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Deps.pm
@@ -0,0 +1,1468 @@
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2012 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you may redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+#########################################################################
+# Several parts are inspired by lib/Dep.pm from lintian (same license)
+#
+# Copyright © 1998 Richard Braakman
+# Portions Copyright © 1999 Darren Benham
+# Portions Copyright © 2000 Sean 'Shaleh' Perry
+# Portions Copyright © 2004 Frank Lichtenheld
+# Portions Copyright © 2006 Russ Allbery
+
+package Dpkg::Deps;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Deps - parse and manipulate dependencies of Debian packages
+
+=head1 DESCRIPTION
+
+The Dpkg::Deps module provides objects implementing various types of
+dependencies.
+
+The most important function is deps_parse(), it turns a dependency line in
+a set of Dpkg::Deps::{Simple,AND,OR,Union} objects depending on the case.
+
+=head1 FUNCTIONS
+
+All the deps_* functions are exported by default.
+
+=over 4
+
+=cut
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+use Dpkg::Version;
+use Dpkg::Arch qw(get_host_arch get_build_arch);
+use Dpkg::BuildProfiles qw(get_build_profiles);
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use Exporter qw(import);
+our @EXPORT = qw(deps_concat deps_parse deps_eval_implication deps_compare);
+
+=item deps_eval_implication($rel_p, $v_p, $rel_q, $v_q)
+
+($rel_p, $v_p) and ($rel_q, $v_q) express two dependencies as (relation,
+version). The relation variable can have the following values that are
+exported by Dpkg::Version: REL_EQ, REL_LT, REL_LE, REL_GT, REL_GT.
+
+This functions returns 1 if the "p" dependency implies the "q"
+dependency. It returns 0 if the "p" dependency implies that "q" is
+not satisfied. It returns undef when there's no implication.
+
+The $v_p and $v_q parameter should be Dpkg::Version objects.
+
+=cut
+
+sub deps_eval_implication {
+    my ($rel_p, $v_p, $rel_q, $v_q) = @_;
+
+    # If versions are not valid, we can't decide of any implication
+    return unless defined($v_p) and $v_p->is_valid();
+    return unless defined($v_q) and $v_q->is_valid();
+
+    # q wants an exact version, so p must provide that exact version.  p
+    # disproves q if q's version is outside the range enforced by p.
+    if ($rel_q eq REL_EQ) {
+        if ($rel_p eq REL_LT) {
+            return ($v_p <= $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_LE) {
+            return ($v_p < $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_GT) {
+            return ($v_p >= $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_GE) {
+            return ($v_p > $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_EQ) {
+            return ($v_p == $v_q);
+        }
+    }
+
+    # A greater than clause may disprove a less than clause. An equal
+    # cause might as well.  Otherwise, if
+    # p's clause is <<, <=, or =, the version must be <= q's to imply q.
+    if ($rel_q eq REL_LE) {
+        if ($rel_p eq REL_GT) {
+            return ($v_p >= $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_GE) {
+            return ($v_p > $v_q) ? 0 : undef;
+	} elsif ($rel_p eq REL_EQ) {
+            return ($v_p <= $v_q) ? 1 : 0;
+        } else { # <<, <=
+            return ($v_p <= $v_q) ? 1 : undef;
+        }
+    }
+
+    # Similar, but << is stronger than <= so p's version must be << q's
+    # version if the p relation is <= or =.
+    if ($rel_q eq REL_LT) {
+        if ($rel_p eq REL_GT or $rel_p eq REL_GE) {
+            return ($v_p >= $v_p) ? 0 : undef;
+        } elsif ($rel_p eq REL_LT) {
+            return ($v_p <= $v_q) ? 1 : undef;
+	} elsif ($rel_p eq REL_EQ) {
+            return ($v_p < $v_q) ? 1 : 0;
+        } else { # <<, <=
+            return ($v_p < $v_q) ? 1 : undef;
+        }
+    }
+
+    # Same logic as above, only inverted.
+    if ($rel_q eq REL_GE) {
+        if ($rel_p eq REL_LT) {
+            return ($v_p <= $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_LE) {
+            return ($v_p < $v_q) ? 0 : undef;
+	} elsif ($rel_p eq REL_EQ) {
+            return ($v_p >= $v_q) ? 1 : 0;
+        } else { # >>, >=
+            return ($v_p >= $v_q) ? 1 : undef;
+        }
+    }
+    if ($rel_q eq REL_GT) {
+        if ($rel_p eq REL_LT or $rel_p eq REL_LE) {
+            return ($v_p <= $v_q) ? 0 : undef;
+        } elsif ($rel_p eq REL_GT) {
+            return ($v_p >= $v_q) ? 1 : undef;
+	} elsif ($rel_p eq REL_EQ) {
+            return ($v_p > $v_q) ? 1 : 0;
+        } else {
+            return ($v_p > $v_q) ? 1 : undef;
+        }
+    }
+
+    return;
+}
+
+=item my $dep = deps_concat(@dep_list)
+
+This function concatenates multiple dependency lines into a single line,
+joining them with ", " if appropriate, and always returning a valid string.
+
+=cut
+
+sub deps_concat {
+    my (@dep_list) = @_;
+
+    return join(', ', grep { defined $_ } @dep_list);
+}
+
+=item my $dep = deps_parse($line, %options)
+
+This function parses the dependency line and returns an object, either a
+Dpkg::Deps::AND or a Dpkg::Deps::Union. Various options can alter the
+behaviour of that function.
+
+=over 4
+
+=item use_arch (defaults to 1)
+
+Take into account the architecture restriction part of the dependencies.
+Set to 0 to completely ignore that information.
+
+=item host_arch (defaults to the current architecture)
+
+Define the host architecture. By default it uses
+Dpkg::Arch::get_host_arch() to identify the proper architecture.
+
+=item build_arch (defaults to the current architecture)
+
+Define the build architecture. By default it uses
+Dpkg::Arch::get_build_arch() to identify the proper architecture.
+
+=item reduce_arch (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current host
+architecture. This implicitely strips off the architecture restriction
+list so that the resulting dependencies are directly applicable to the
+current architecture.
+
+=item use_profiles (defaults to 1)
+
+Take into account the profile restriction part of the dependencies. Set
+to 0 to completely ignore that information.
+
+=item build_profiles (defaults to no profile)
+
+Define the active build profiles. By default no profile is defined.
+
+=item reduce_profiles (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current build
+profile. This implicitly strips off the profile restriction list so
+that the resulting dependencies are directly applicable to the current
+profiles.
+
+=item reduce_restrictions (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current set of
+restrictions. This implicitly strips off any restriction list so that the
+resulting dependencies are directly applicable to the current restriction.
+This currently implies C<reduce_arch> and C<reduce_profiles>, and overrides
+them if set.
+
+=item union (defaults to 0)
+
+If set to 1, returns a Dpkg::Deps::Union instead of a Dpkg::Deps::AND. Use
+this when parsing non-dependency fields like Conflicts.
+
+=item build_dep (defaults to 0)
+
+If set to 1, allow build-dep only arch qualifiers, that is “:native”.
+This should be set whenever working with build-deps.
+
+=back
+
+=cut
+
+sub deps_parse {
+    my $dep_line = shift;
+    my %options = (@_);
+    $options{use_arch} = 1 if not exists $options{use_arch};
+    $options{reduce_arch} = 0 if not exists $options{reduce_arch};
+    $options{host_arch} = get_host_arch() if not exists $options{host_arch};
+    $options{build_arch} = get_build_arch() if not exists $options{build_arch};
+    $options{use_profiles} = 1 if not exists $options{use_profiles};
+    $options{reduce_profiles} = 0 if not exists $options{reduce_profiles};
+    $options{build_profiles} = [ get_build_profiles() ]
+        if not exists $options{build_profiles};
+    $options{reduce_restrictions} = 0 if not exists $options{reduce_restrictions};
+    $options{union} = 0 if not exists $options{union};
+    $options{build_dep} = 0 if not exists $options{build_dep};
+
+    if ($options{reduce_restrictions}) {
+        $options{reduce_arch} = 1;
+        $options{reduce_profiles} = 1;
+    }
+
+    # Strip trailing/leading spaces
+    $dep_line =~ s/^\s+//;
+    $dep_line =~ s/\s+$//;
+
+    my @dep_list;
+    foreach my $dep_and (split(/\s*,\s*/m, $dep_line)) {
+        my @or_list = ();
+        foreach my $dep_or (split(/\s*\|\s*/m, $dep_and)) {
+	    my $dep_simple = Dpkg::Deps::Simple->new($dep_or, host_arch =>
+	                                             $options{host_arch},
+	                                             build_arch =>
+	                                             $options{build_arch},
+	                                             build_dep =>
+	                                             $options{build_dep});
+	    if (not defined $dep_simple->{package}) {
+		warning(_g("can't parse dependency %s"), $dep_or);
+		return;
+	    }
+	    $dep_simple->{arches} = undef if not $options{use_arch};
+            if ($options{reduce_arch}) {
+		$dep_simple->reduce_arch($options{host_arch});
+		next if not $dep_simple->arch_is_concerned($options{host_arch});
+	    }
+	    $dep_simple->{restrictions} = undef if not $options{use_profiles};
+	    if ($options{reduce_profiles}) {
+		$dep_simple->reduce_profiles($options{build_profiles});
+		next if not $dep_simple->profile_is_concerned($options{build_profiles});
+	    }
+	    push @or_list, $dep_simple;
+        }
+	next if not @or_list;
+	if (scalar @or_list == 1) {
+	    push @dep_list, $or_list[0];
+	} else {
+	    my $dep_or = Dpkg::Deps::OR->new();
+	    $dep_or->add($_) foreach (@or_list);
+	    push @dep_list, $dep_or;
+	}
+    }
+    my $dep_and;
+    if ($options{union}) {
+	$dep_and = Dpkg::Deps::Union->new();
+    } else {
+	$dep_and = Dpkg::Deps::AND->new();
+    }
+    foreach my $dep (@dep_list) {
+        if ($options{union} and not $dep->isa('Dpkg::Deps::Simple')) {
+            warning(_g('an union dependency can only contain simple dependencies'));
+            return;
+        }
+        $dep_and->add($dep);
+    }
+    return $dep_and;
+}
+
+=item deps_compare($a, $b)
+
+Implements a comparison operator between two dependency objects.
+This function is mainly used to implement the sort() method.
+
+=back
+
+=cut
+
+my %relation_ordering = (
+	undef => 0,
+	REL_GE() => 1,
+	REL_GT() => 2,
+	REL_EQ() => 3,
+	REL_LT() => 4,
+	REL_LE() => 5,
+);
+
+sub deps_compare {
+    my ($a, $b) = @_;
+    return -1 if $a->is_empty();
+    return 1 if $b->is_empty();
+    while ($a->isa('Dpkg::Deps::Multiple')) {
+	return -1 if $a->is_empty();
+	my @deps = $a->get_deps();
+	$a = $deps[0];
+    }
+    while ($b->isa('Dpkg::Deps::Multiple')) {
+	return 1 if $b->is_empty();
+	my @deps = $b->get_deps();
+	$b = $deps[0];
+    }
+    my $ar = defined($a->{relation}) ? $a->{relation} : 'undef';
+    my $br = defined($b->{relation}) ? $b->{relation} : 'undef';
+    return (($a->{package} cmp $b->{package}) ||
+	    ($relation_ordering{$ar} <=> $relation_ordering{$br}) ||
+	    ($a->{version} cmp $b->{version}));
+}
+
+
+package Dpkg::Deps::Simple;
+
+=head1 OBJECTS - Dpkg::Deps::*
+
+There are several kind of dependencies. A Dpkg::Deps::Simple dependency
+represents a single dependency statement (it relates to one package only).
+Dpkg::Deps::Multiple dependencies are built on top of this object
+and combine several dependencies in a different manners. Dpkg::Deps::AND
+represents the logical "AND" between dependencies while Dpkg::Deps::OR
+represents the logical "OR". Dpkg::Deps::Multiple objects can contain
+Dpkg::Deps::Simple object as well as other Dpkg::Deps::Multiple objects.
+
+In practice, the code is only meant to handle the realistic cases which,
+given Debian's dependencies structure, imply those restrictions: AND can
+contain Simple or OR objects, OR can only contain Simple objects.
+
+Dpkg::Deps::KnownFacts is a special object that is used while evaluating
+dependencies and while trying to simplify them. It represents a set of
+installed packages along with the virtual packages that they might
+provide.
+
+=head2 COMMON FUNCTIONS
+
+=over 4
+
+=item $dep->is_empty()
+
+Returns true if the dependency is empty and doesn't contain any useful
+information. This is true when a Dpkg::Deps::Simple object has not yet
+been initialized or when a (descendant of) Dpkg::Deps::Multiple contains
+an empty list of dependencies.
+
+=item $dep->get_deps()
+
+Returns a list of sub-dependencies. For Dpkg::Deps::Simple it returns
+itself.
+
+=item $dep->output([$fh])
+
+=item "$dep"
+
+Returns a string representing the dependency. If $fh is set, it prints
+the string to the filehandle.
+
+=item $dep->implies($other_dep)
+
+Returns 1 when $dep implies $other_dep. Returns 0 when $dep implies
+NOT($other_dep). Returns undef when there's no implication. $dep and
+$other_dep do not need to be of the same type.
+
+=item $dep->sort()
+
+Sorts alphabetically the internal list of dependencies. It's a no-op for
+Dpkg::Deps::Simple objects.
+
+=item $dep->arch_is_concerned($arch)
+
+Returns true if the dependency applies to the indicated architecture. For
+multiple dependencies, it returns true if at least one of the
+sub-dependencies apply to this architecture.
+
+=item $dep->reduce_arch($arch)
+
+Simplifies the dependency to contain only information relevant to the given
+architecture. A Dpkg::Deps::Simple object can be left empty after this
+operation. For Dpkg::Deps::Multiple objects, the non-relevant
+sub-dependencies are simply removed.
+
+This trims off the architecture restriction list of Dpkg::Deps::Simple
+objects.
+
+=item $dep->get_evaluation($facts)
+
+Evaluates the dependency given a list of installed packages and a list of
+virtual packages provided. Those lists are part of the
+Dpkg::Deps::KnownFacts object given as parameters.
+
+Returns 1 when it's true, 0 when it's false, undef when some information
+is lacking to conclude.
+
+=item $dep->simplify_deps($facts, @assumed_deps)
+
+Simplifies the dependency as much as possible given the list of facts (see
+object Dpkg::Deps::KnownFacts) and a list of other dependencies that are
+known to be true.
+
+=item $dep->has_arch_restriction()
+
+For a simple dependency, returns the package name if the dependency
+applies only to a subset of architectures.  For multiple dependencies, it
+returns the list of package names that have such a restriction.
+
+=item $dep->reset()
+
+Clears any dependency information stored in $dep so that $dep->is_empty()
+returns true.
+
+=back
+
+=head2 Dpkg::Deps::Simple
+
+Such an object has four interesting properties:
+
+=over 4
+
+=item package
+
+The package name (can be undef if the dependency has not been initialized
+or if the simplification of the dependency lead to its removal).
+
+=item relation
+
+The relational operator: "=", "<<", "<=", ">=" or ">>". It can be
+undefined if the dependency had no version restriction. In that case the
+following field is also undefined.
+
+=item version
+
+The version.
+
+=item arches
+
+The list of architectures where this dependency is applicable. It's
+undefined when there's no restriction, otherwise it's an
+array ref. It can contain an exclusion list, in that case each
+architecture is prefixed with an exclamation mark.
+
+=item archqual
+
+The arch qualifier of the dependency (can be undef if there's none).
+In the dependency "python:any (>= 2.6)", the arch qualifier is "any".
+
+=back
+
+=head3 METHODS
+
+=over 4
+
+=item $simple_dep->parse_string('dpkg-dev (>= 1.14.8) [!hurd-i386]')
+
+Parses the dependency and modifies internal properties to match the parsed
+dependency.
+
+=item $simple_dep->merge_union($other_dep)
+
+Returns true if $simple_dep could be modified to represent the union of
+both dependencies. Otherwise returns false.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Carp;
+
+use Dpkg::Arch qw(debarch_is);
+use Dpkg::Version;
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::Util qw(:list);
+
+use parent qw(Dpkg::Interface::Storable);
+
+sub new {
+    my ($this, $arg, %opts) = @_;
+    my $class = ref($this) || $this;
+    my $self = {};
+    bless $self, $class;
+    $self->reset();
+    $self->{host_arch} = $opts{host_arch} || Dpkg::Arch::get_host_arch();
+    $self->{build_arch} = $opts{build_arch} || Dpkg::Arch::get_build_arch();
+    $self->{build_dep} = $opts{build_dep} || 0;
+    $self->parse_string($arg) if defined($arg);
+    return $self;
+}
+
+sub reset {
+    my ($self) = @_;
+    $self->{package} = undef;
+    $self->{relation} = undef;
+    $self->{version} = undef;
+    $self->{arches} = undef;
+    $self->{archqual} = undef;
+    $self->{restrictions} = undef;
+}
+
+sub parse {
+    my ($self, $fh, $desc) = @_;
+    my $line = <$fh>;
+    chomp($line);
+    return $self->parse_string($line);
+}
+
+sub parse_string {
+    my ($self, $dep) = @_;
+    return if not $dep =~
+           m{^\s*                           # skip leading whitespace
+              ([a-zA-Z0-9][a-zA-Z0-9+.-]*)  # package name
+              (?:                           # start of optional part
+                :                           # colon for architecture
+                ([a-zA-Z0-9][a-zA-Z0-9-]*)  # architecture name
+              )?                            # end of optional part
+              (?:                           # start of optional part
+                \s* \(                      # open parenthesis for version part
+                \s* (<<|<=|=|>=|>>|<|>)     # relation part
+                \s* (.*?)                   # do not attempt to parse version
+                \s* \)                      # closing parenthesis
+              )?                            # end of optional part
+              (?:                           # start of optional architecture
+                \s* \[                      # open bracket for architecture
+                \s* (.*?)                   # don't parse architectures now
+                \s* \]                      # closing bracket
+              )?                            # end of optional architecture
+              (?:                           # start of optional restriction
+                \s* <                       # open bracket for restriction
+                \s* (.*?)                   # don't parse restrictions now
+                \s* >                       # closing bracket
+              )?                            # end of optional restriction
+              \s*$                          # trailing spaces at end
+            }x;
+    if (defined($2)) {
+	return if $2 eq 'native' and not $self->{build_dep};
+	$self->{archqual} = $2;
+    }
+    $self->{package} = $1;
+    $self->{relation} = version_normalize_relation($3) if defined($3);
+    if (defined($4)) {
+	$self->{version} = Dpkg::Version->new($4);
+    }
+    if (defined($5)) {
+	$self->{arches} = [ split(/\s+/, $5) ];
+    }
+    if (defined($6)) {
+	$self->{restrictions} = [ map { lc } split /\s+/, $6 ];
+    }
+}
+
+sub output {
+    my ($self, $fh) = @_;
+    my $res = $self->{package};
+    if (defined($self->{archqual})) {
+	$res .= ':' . $self->{archqual};
+    }
+    if (defined($self->{relation})) {
+	$res .= ' (' . $self->{relation} . ' ' . $self->{version} .  ')';
+    }
+    if (defined($self->{arches})) {
+	$res .= ' [' . join(' ', @{$self->{arches}}) . ']';
+    }
+    if (defined($self->{restrictions})) {
+	$res .= ' <' . join(' ', @{$self->{restrictions}}) . '>';
+    }
+    if (defined($fh)) {
+	print { $fh } $res;
+    }
+    return $res;
+}
+
+# _arch_is_superset(\@p, \@q)
+#
+# Returns true if the arch list @p is a superset of arch list @q.
+# The arguments can also be undef in case there's no explicit architecture
+# restriction.
+sub _arch_is_superset {
+    my ($p, $q) = @_;
+    my $p_arch_neg = defined($p) && $p->[0] =~ /^!/;
+    my $q_arch_neg = defined($q) && $q->[0] =~ /^!/;
+
+    # If "p" has no arches, it is a superset of q and we should fall through
+    # to the version check.
+    if (not defined $p) {
+	return 1;
+    }
+
+    # If q has no arches, it is a superset of p and there are no useful
+    # implications.
+    elsif (not defined $q) {
+	return 0;
+    }
+
+    # Both have arches.  If neither are negated, we know nothing useful
+    # unless q is a subset of p.
+    elsif (not $p_arch_neg and not $q_arch_neg) {
+	my %p_arches = map { $_ => 1 } @{$p};
+	my $subset = 1;
+	for my $arch (@{$q}) {
+	    $subset = 0 unless $p_arches{$arch};
+	}
+	return 0 unless $subset;
+    }
+
+    # If both are negated, we know nothing useful unless p is a subset of
+    # q (and therefore has fewer things excluded, and therefore is more
+    # general).
+    elsif ($p_arch_neg and $q_arch_neg) {
+	my %q_arches = map { $_ => 1 } @{$q};
+	my $subset = 1;
+	for my $arch (@{$p}) {
+	    $subset = 0 unless $q_arches{$arch};
+	}
+	return 0 unless $subset;
+    }
+
+    # If q is negated and p isn't, we'd need to know the full list of
+    # arches to know if there's any relationship, so bail.
+    elsif (not $p_arch_neg and $q_arch_neg) {
+	return 0;
+    }
+
+    # If p is negated and q isn't, q is a subset of p if none of the
+    # negated arches in p are present in q.
+    elsif ($p_arch_neg and not $q_arch_neg) {
+	my %q_arches = map { $_ => 1 } @{$q};
+	my $subset = 1;
+	for my $arch (@{$p}) {
+	    $subset = 0 if $q_arches{substr($arch, 1)};
+	}
+	return 0 unless $subset;
+    }
+    return 1;
+}
+
+# _arch_qualifier_allows_implication($p, $q)
+#
+# Returns true if the arch qualifier $p and $q are compatible with the
+# implication $p -> $q, false otherwise. $p/$q can be
+# undef/"any"/"native" or an architecture string.
+sub _arch_qualifier_allows_implication {
+    my ($p, $q) = @_;
+    if (defined $p and $p eq 'any') {
+	return 1 if defined $q and $q eq 'any';
+	return 0;
+    } elsif (defined $p and $p eq 'native') {
+	return 1 if defined $q and ($q eq 'any' or $q eq 'native');
+	return 0;
+    } elsif (defined $p) {
+	return 1 if defined $q and ($p eq $q or $q eq 'any');
+	return 0;
+    } else {
+	return 0 if defined $q and $q ne 'any' and $q ne 'native';
+	return 1;
+    }
+}
+
+# Returns true if the dependency in parameter can deduced from the current
+# dependency. Returns false if it can be negated. Returns undef if nothing
+# can be concluded.
+sub implies {
+    my ($self, $o) = @_;
+    if ($o->isa('Dpkg::Deps::Simple')) {
+	# An implication is only possible on the same package
+	return if $self->{package} ne $o->{package};
+
+	# Our architecture set must be a superset of the architectures for
+	# o, otherwise we can't conclude anything.
+	return unless _arch_is_superset($self->{arches}, $o->{arches});
+
+	# The arch qualifier must not forbid an implication
+	return unless _arch_qualifier_allows_implication($self->{archqual},
+	                                                 $o->{archqual});
+
+	# If o has no version clause, then our dependency is stronger
+	return 1 if not defined $o->{relation};
+	# If o has a version clause, we must also have one, otherwise there
+	# can't be an implication
+	return if not defined $self->{relation};
+
+	return Dpkg::Deps::deps_eval_implication($self->{relation},
+		$self->{version}, $o->{relation}, $o->{version});
+
+    } elsif ($o->isa('Dpkg::Deps::AND')) {
+	# TRUE: Need to imply all individual elements
+	# FALSE: Need to NOT imply at least one individual element
+	my $res = 1;
+	foreach my $dep ($o->get_deps()) {
+	    my $implication = $self->implies($dep);
+	    unless (defined($implication) && $implication == 1) {
+		$res = $implication;
+		last if defined $res;
+	    }
+	}
+	return $res;
+    } elsif ($o->isa('Dpkg::Deps::OR')) {
+	# TRUE: Need to imply at least one individual element
+	# FALSE: Need to not apply all individual elements
+	# UNDEF: The rest
+	my $res = undef;
+	foreach my $dep ($o->get_deps()) {
+	    my $implication = $self->implies($dep);
+	    if (defined($implication)) {
+		if (not defined $res) {
+		    $res = $implication;
+		} else {
+		    if ($implication) {
+			$res = 1;
+		    } else {
+			$res = 0;
+		    }
+		}
+		last if defined($res) && $res == 1;
+	    }
+	}
+	return $res;
+    } else {
+	croak 'Dpkg::Deps::Simple cannot evaluate implication with a ' .
+	      ref($o);
+    }
+}
+
+sub get_deps {
+    my $self = shift;
+    return $self;
+}
+
+sub sort {
+    # Nothing to sort
+}
+
+sub arch_is_concerned {
+    my ($self, $host_arch) = @_;
+
+    return 0 if not defined $self->{package}; # Empty dep
+    return 1 if not defined $self->{arches};  # Dep without arch spec
+
+    my $seen_arch = 0;
+    foreach my $arch (@{$self->{arches}}) {
+	$arch=lc($arch);
+
+	if ($arch =~ /^!/) {
+	    my $not_arch = $arch;
+	    $not_arch =~ s/^!//;
+
+	    if (debarch_is($host_arch, $not_arch)) {
+		$seen_arch = 0;
+		last;
+	    } else {
+		# !arch includes by default all other arches
+		# unless they also appear in a !otherarch
+		$seen_arch = 1;
+	    }
+	} elsif (debarch_is($host_arch, $arch)) {
+	    $seen_arch = 1;
+	    last;
+	}
+    }
+    return $seen_arch;
+}
+
+sub reduce_arch {
+    my ($self, $host_arch) = @_;
+    if (not $self->arch_is_concerned($host_arch)) {
+	$self->reset();
+    } else {
+	$self->{arches} = undef;
+    }
+}
+
+sub has_arch_restriction {
+    my ($self) = @_;
+    if (defined $self->{arches}) {
+	return $self->{package};
+    } else {
+	return ();
+    }
+}
+
+sub profile_is_concerned {
+    my ($self, $build_profiles) = @_;
+
+    return 0 if not defined $self->{package}; # Empty dep
+    return 1 if not defined $self->{restrictions}; # Dep without restrictions
+
+    my $seen_profile = 0;
+    foreach my $restriction (@{$self->{restrictions}}) {
+        # Determine if this restriction is negated, and within the "profile"
+        # namespace, otherwise it does not concern this check.
+        next if $restriction !~ m/^(!)?profile\.(.*)/;
+
+        my $negated = defined $1 && $1 eq '!';
+        my $profile = $2;
+
+        # Determine if the restriction matches any of the specified profiles.
+        my $found = any { $_ eq $profile } @{$build_profiles};
+
+        if ($negated) {
+            if ($found) {
+                $seen_profile = 0;
+                last;
+            } else {
+                # "!profile.this" includes by default all other profiles
+                # unless they also appear in a "!profile.other".
+                $seen_profile = 1;
+            }
+        } elsif ($found) {
+            $seen_profile = 1;
+            last;
+        }
+    }
+    return $seen_profile;
+}
+
+sub reduce_profiles {
+    my ($self, $build_profiles) = @_;
+
+    if (not $self->profile_is_concerned($build_profiles)) {
+        $self->reset();
+    } else {
+        $self->{restrictions} = undef;
+    }
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    return if not defined $self->{package};
+    return $facts->_evaluate_simple_dep($self);
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+    my $eval = $self->get_evaluation($facts);
+    $self->reset() if defined $eval and $eval == 1;
+}
+
+sub is_empty {
+    my $self = shift;
+    return not defined $self->{package};
+}
+
+sub merge_union {
+    my ($self, $o) = @_;
+    return 0 if not $o->isa('Dpkg::Deps::Simple');
+    return 0 if $self->is_empty() or $o->is_empty();
+    return 0 if $self->{package} ne $o->{package};
+    return 0 if defined $self->{arches} or defined $o->{arches};
+
+    if (not defined $o->{relation} and defined $self->{relation}) {
+	# Union is the non-versioned dependency
+	$self->{relation} = undef;
+	$self->{version} = undef;
+	return 1;
+    }
+
+    my $implication = $self->implies($o);
+    my $rev_implication = $o->implies($self);
+    if (defined($implication)) {
+	if ($implication) {
+	    $self->{relation} = $o->{relation};
+	    $self->{version} = $o->{version};
+	    return 1;
+	} else {
+	    return 0;
+	}
+    }
+    if (defined($rev_implication)) {
+	if ($rev_implication) {
+	    # Already merged...
+	    return 1;
+	} else {
+	    return 0;
+	}
+    }
+    return 0;
+}
+
+package Dpkg::Deps::Multiple;
+
+=head2 Dpkg::Deps::Multiple
+
+This is the base class for Dpkg::Deps::{AND,OR,Union}. It implements
+the following methods:
+
+=over 4
+
+=item $mul->add($dep)
+
+Adds a new dependency object at the end of the list.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use Carp;
+
+use Dpkg::ErrorHandling;
+
+use parent qw(Dpkg::Interface::Storable);
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = { list => [ @_ ] };
+    bless $self, $class;
+    return $self;
+}
+
+sub reset {
+    my ($self) = @_;
+    $self->{list} = [];
+}
+
+sub add {
+    my $self = shift;
+    push @{$self->{list}}, @_;
+}
+
+sub get_deps {
+    my $self = shift;
+    return grep { not $_->is_empty() } @{$self->{list}};
+}
+
+sub sort {
+    my $self = shift;
+    my @res = ();
+    @res = sort { Dpkg::Deps::deps_compare($a, $b) } @{$self->{list}};
+    $self->{list} = [ @res ];
+}
+
+sub arch_is_concerned {
+    my ($self, $host_arch) = @_;
+    my $res = 0;
+    foreach my $dep (@{$self->{list}}) {
+	$res = 1 if $dep->arch_is_concerned($host_arch);
+    }
+    return $res;
+}
+
+sub reduce_arch {
+    my ($self, $host_arch) = @_;
+    my @new;
+    foreach my $dep (@{$self->{list}}) {
+	$dep->reduce_arch($host_arch);
+	push @new, $dep if $dep->arch_is_concerned($host_arch);
+    }
+    $self->{list} = [ @new ];
+}
+
+sub has_arch_restriction {
+    my ($self) = @_;
+    my @res;
+    foreach my $dep (@{$self->{list}}) {
+	push @res, $dep->has_arch_restriction();
+    }
+    return @res;
+}
+
+
+sub is_empty {
+    my $self = shift;
+    return scalar @{$self->{list}} == 0;
+}
+
+sub merge_union {
+    croak 'method merge_union() is only valid for Dpkg::Deps::Simple';
+}
+
+package Dpkg::Deps::AND;
+
+=head2 Dpkg::Deps::AND
+
+This object represents a list of dependencies who must be met at the same
+time.
+
+=over 4
+
+=item $and->output([$fh])
+
+The output method uses ", " to join the list of sub-dependencies.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use parent -norequire, qw(Dpkg::Deps::Multiple);
+
+sub output {
+    my ($self, $fh) = @_;
+    my $res = join(', ', map { $_->output() } grep { not $_->is_empty() } $self->get_deps());
+    if (defined($fh)) {
+	print { $fh } $res;
+    }
+    return $res;
+}
+
+sub implies {
+    my ($self, $o) = @_;
+    # If any individual member can imply $o or NOT $o, we're fine
+    foreach my $dep ($self->get_deps()) {
+	my $implication = $dep->implies($o);
+	return 1 if defined($implication) && $implication == 1;
+	return 0 if defined($implication) && $implication == 0;
+    }
+    # If o is an AND, we might have an implication, if we find an
+    # implication within us for each predicate in o
+    if ($o->isa('Dpkg::Deps::AND')) {
+	my $subset = 1;
+	foreach my $odep ($o->get_deps()) {
+	    my $found = 0;
+	    foreach my $dep ($self->get_deps()) {
+		$found = 1 if $dep->implies($odep);
+	    }
+	    $subset = 0 if not $found;
+	}
+	return 1 if $subset;
+    }
+    return;
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    # Return 1 only if all members evaluates to true
+    # Return 0 if at least one member evaluates to false
+    # Return undef otherwise
+    my $result = 1;
+    foreach my $dep ($self->get_deps()) {
+	my $eval = $dep->get_evaluation($facts);
+	if (not defined $eval) {
+	    $result = undef;
+	} elsif ($eval == 0) {
+	    $result = 0;
+	    last;
+	} elsif ($eval == 1) {
+	    # Still possible
+	}
+    }
+    return $result;
+}
+
+sub simplify_deps {
+    my ($self, $facts, @knowndeps) = @_;
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $dep = shift @{$self->{list}};
+	my $eval = $dep->get_evaluation($facts);
+	next if defined($eval) and $eval == 1;
+	foreach my $odep (@knowndeps, @new) {
+	    next WHILELOOP if $odep->implies($dep);
+	}
+        # When a dependency is implied by another dependency that
+        # follows, then invert them
+        # "a | b, c, a"  becomes "a, c" and not "c, a"
+        my $i = 0;
+	foreach my $odep (@{$self->{list}}) {
+            if (defined $odep and $odep->implies($dep)) {
+                splice @{$self->{list}}, $i, 1;
+                unshift @{$self->{list}}, $odep;
+                next WHILELOOP;
+            }
+            $i++;
+	}
+	push @new, $dep;
+    }
+    $self->{list} = [ @new ];
+}
+
+
+package Dpkg::Deps::OR;
+
+=head2 Dpkg::Deps::OR
+
+This object represents a list of dependencies of which only one must be met
+for the dependency to be true.
+
+=over 4
+
+=item $or->output([$fh])
+
+The output method uses " | " to join the list of sub-dependencies.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use parent -norequire, qw(Dpkg::Deps::Multiple);
+
+sub output {
+    my ($self, $fh) = @_;
+    my $res = join(' | ', map { $_->output() } grep { not $_->is_empty() } $self->get_deps());
+    if (defined($fh)) {
+	print { $fh } $res;
+    }
+    return $res;
+}
+
+sub implies {
+    my ($self, $o) = @_;
+
+    # Special case for AND with a single member, replace it by its member
+    if ($o->isa('Dpkg::Deps::AND')) {
+	my @subdeps = $o->get_deps();
+	if (scalar(@subdeps) == 1) {
+	    $o = $subdeps[0];
+	}
+    }
+
+    # In general, an OR dependency can't imply anything except if each
+    # of its member implies a member in the other OR dependency
+    if ($o->isa('Dpkg::Deps::OR')) {
+	my $subset = 1;
+	foreach my $dep ($self->get_deps()) {
+	    my $found = 0;
+	    foreach my $odep ($o->get_deps()) {
+		$found = 1 if $dep->implies($odep);
+	    }
+	    $subset = 0 if not $found;
+	}
+	return 1 if $subset;
+    }
+    return;
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    # Returns false if all members evaluates to 0
+    # Returns true if at least one member evaluates to true
+    # Returns undef otherwise
+    my $result = 0;
+    foreach my $dep ($self->get_deps()) {
+	my $eval = $dep->get_evaluation($facts);
+	if (not defined $eval) {
+	    $result = undef;
+	} elsif ($eval == 1) {
+	    $result = 1;
+	    last;
+	} elsif ($eval == 0) {
+	    # Still possible to have a false evaluation
+	}
+    }
+    return $result;
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $dep = shift @{$self->{list}};
+	my $eval = $dep->get_evaluation($facts);
+	if (defined($eval) and $eval == 1) {
+	    $self->{list} = [];
+	    return;
+	}
+	foreach my $odep (@new, @{$self->{list}}) {
+	    next WHILELOOP if $odep->implies($dep);
+	}
+	push @new, $dep;
+    }
+    $self->{list} = [ @new ];
+}
+
+package Dpkg::Deps::Union;
+
+=head2 Dpkg::Deps::Union
+
+This object represents a list of relationships.
+
+=over 4
+
+=item $union->output([$fh])
+
+The output method uses ", " to join the list of relationships.
+
+=item $union->implies($other_dep)
+
+=item $union->get_evaluation($other_dep)
+
+Those methods are not meaningful for this object and always return undef.
+
+=item $union->simplify_deps($facts)
+
+The simplication is done to generate an union of all the relationships.
+It uses $simple_dep->merge_union($other_dep) to get its job done.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+
+use parent -norequire, qw(Dpkg::Deps::Multiple);
+
+sub output {
+    my ($self, $fh) = @_;
+    my $res = join(', ', map { $_->output() } grep { not $_->is_empty() } $self->get_deps());
+    if (defined($fh)) {
+	print { $fh } $res;
+    }
+    return $res;
+}
+
+sub implies {
+    # Implication test are not useful on Union
+    return;
+}
+
+sub get_evaluation {
+    # Evaluation are not useful on Union
+    return;
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $odep = shift @{$self->{list}};
+	foreach my $dep (@new) {
+	    next WHILELOOP if $dep->merge_union($odep);
+	}
+	push @new, $odep;
+    }
+    $self->{list} = [ @new ];
+}
+
+package Dpkg::Deps::KnownFacts;
+
+=head2 Dpkg::Deps::KnownFacts
+
+This object represents a list of installed packages and a list of virtual
+packages provided (by the set of installed packages).
+
+=over 4
+
+=item my $facts = Dpkg::Deps::KnownFacts->new();
+
+Creates a new object.
+
+=cut
+
+use strict;
+use warnings;
+
+use Dpkg::Version;
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = {
+	pkg => {},
+	virtualpkg => {},
+    };
+    bless $self, $class;
+    return $self;
+}
+
+=item $facts->add_installed_package($package, $version, $arch, $multiarch)
+
+Records that the given version of the package is installed. If
+$version/$arch is undefined we know that the package is installed but we
+don't know which version/architecture it is. $multiarch is the Multi-Arch
+field of the package. If $multiarch is undef, it will be equivalent to
+"Multi-Arch: no".
+
+Note that $multiarch is only used if $arch is provided.
+
+=cut
+
+sub add_installed_package {
+    my ($self, $pkg, $ver, $arch, $multiarch) = @_;
+    my $p = {
+	package => $pkg,
+	version => $ver,
+	architecture => $arch,
+	multiarch => $multiarch || 'no',
+    };
+    $self->{pkg}{"$pkg:$arch"} = $p if defined $arch;
+    push @{$self->{pkg}{$pkg}}, $p;
+}
+
+=item $facts->add_provided_package($virtual, $relation, $version, $by)
+
+Records that the "$by" package provides the $virtual package. $relation
+and $version correspond to the associated relation given in the Provides
+field. This might be used in the future for versioned provides.
+
+=cut
+
+sub add_provided_package {
+    my ($self, $pkg, $rel, $ver, $by) = @_;
+    if (not exists $self->{virtualpkg}{$pkg}) {
+	$self->{virtualpkg}{$pkg} = [];
+    }
+    push @{$self->{virtualpkg}{$pkg}}, [ $by, $rel, $ver ];
+}
+
+=item my ($check, $param) = $facts->check_package($package)
+
+$check is one when the package is found. For a real package, $param
+contains the version. For a virtual package, $param contains an array
+reference containing the list of packages that provide it (each package is
+listed as [ $provider, $relation, $version ]).
+
+This function is obsolete and should not be used. Dpkg::Deps::KnownFacts
+is only meant to be filled with data and then passed to Dpkg::Deps
+methods where appropriate, but it should not be directly queried.
+
+=back
+
+=cut
+
+sub check_package {
+    my ($self, $pkg) = @_;
+    if (exists $self->{pkg}{$pkg}) {
+	return (1, $self->{pkg}{$pkg}[0]{version});
+    }
+    if (exists $self->{virtualpkg}{$pkg}) {
+	return (1, $self->{virtualpkg}{$pkg});
+    }
+    return (0, undef);
+}
+
+## The functions below are private to Dpkg::Deps
+
+sub _find_package {
+    my ($self, $dep, $lackinfos) = @_;
+    my ($pkg, $archqual) = ($dep->{package}, $dep->{archqual});
+    return if not exists $self->{pkg}{$pkg};
+    my $host_arch = $dep->{host_arch};
+    my $build_arch = $dep->{build_arch};
+    foreach my $p (@{$self->{pkg}{$pkg}}) {
+	my $a = $p->{architecture};
+	my $ma = $p->{multiarch};
+	if (not defined $a) {
+	    $$lackinfos = 1;
+	    next;
+	}
+	if (not defined $archqual) {
+	    return $p if $ma eq 'foreign';
+	    return $p if $a eq $host_arch or $a eq 'all';
+	} elsif ($archqual eq 'any') {
+	    return $p if $ma eq 'allowed';
+	} elsif ($archqual eq 'native') {
+	    return $p if $a eq $build_arch and $ma ne 'foreign';
+	} else {
+	    return $p if $a eq $archqual;
+	}
+    }
+    return;
+}
+
+sub _find_virtual_packages {
+    my ($self, $pkg) = @_;
+    return () if not exists $self->{virtualpkg}{$pkg};
+    return @{$self->{virtualpkg}{$pkg}};
+}
+
+sub _evaluate_simple_dep {
+    my ($self, $dep) = @_;
+    my ($lackinfos, $pkg) = (0, $dep->{package});
+    my $p = $self->_find_package($dep, \$lackinfos);
+    if ($p) {
+	if (defined $dep->{relation}) {
+	    if (defined $p->{version}) {
+		return 1 if version_compare_relation($p->{version},
+		                           $dep->{relation}, $dep->{version});
+	    } else {
+		$lackinfos = 1;
+	    }
+	} else {
+	    return 1;
+	}
+    }
+    foreach my $virtpkg ($self->_find_virtual_packages($pkg)) {
+	# XXX: Adapt when versioned provides are allowed
+	next if defined $virtpkg->[1];
+	next if defined $dep->{relation}; # Provides don't satisfy versioned deps
+	return 1;
+    }
+    return if $lackinfos;
+    return 0;
+}
+
+=head1 CHANGES
+
+=head2 Version 1.02
+
+=over
+
+=item * Add new Dpkg::deps_concat() function.
+
+=back
+
+=head2 Version 1.01
+
+=over
+
+=item * Add new $dep->reset() method that all dependency objects support.
+
+=item * Dpkg::Deps::Simple now recognizes the arch qualifier "any" and
+stores it in the "archqual" property when present.
+
+=item * Dpkg::Deps::KnownFacts->add_installed_package() now accepts 2
+supplementary parameters ($arch and $multiarch).
+
+=item * Dpkg::Deps::KnownFacts->check_package() is obsolete, it should
+not have been part of the public API.
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/ErrorHandling.pm b/third_party/dpkg-dev/scripts/Dpkg/ErrorHandling.pm
new file mode 100644
index 0000000..275c506
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/ErrorHandling.pm
@@ -0,0 +1,107 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::ErrorHandling;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.02';
+
+use Dpkg ();
+use Dpkg::Gettext;
+
+use Exporter qw(import);
+our @EXPORT = qw(report_options info warning error errormsg
+                 syserr subprocerr usageerr);
+our @EXPORT_OK = qw(report);
+
+my $quiet_warnings = 0;
+my $info_fh = \*STDOUT;
+
+sub report_options
+{
+    my (%options) = @_;
+
+    if (exists $options{quiet_warnings}) {
+        $quiet_warnings = $options{quiet_warnings};
+    }
+    if (exists $options{info_fh}) {
+        $info_fh = $options{info_fh};
+    }
+}
+
+sub report(@)
+{
+    my ($type, $msg) = (shift, shift);
+
+    $msg = sprintf($msg, @_) if (@_);
+    return "$Dpkg::PROGNAME: $type: $msg\n";
+}
+
+sub info($;@)
+{
+    print { $info_fh } report(_g('info'), @_) if (!$quiet_warnings);
+}
+
+sub warning($;@)
+{
+    warn report(_g('warning'), @_) if (!$quiet_warnings);
+}
+
+sub syserr($;@)
+{
+    my $msg = shift;
+    die report(_g('error'), "$msg: $!", @_);
+}
+
+sub error($;@)
+{
+    die report(_g('error'), @_);
+}
+
+sub errormsg($;@)
+{
+    print { *STDERR } report(_g('error'), @_);
+}
+
+sub subprocerr(@)
+{
+    my ($p) = (shift);
+
+    $p = sprintf($p, @_) if (@_);
+
+    require POSIX;
+
+    if (POSIX::WIFEXITED($?)) {
+	error(_g('%s gave error exit status %s'), $p, POSIX::WEXITSTATUS($?));
+    } elsif (POSIX::WIFSIGNALED($?)) {
+	error(_g('%s died from signal %s'), $p, POSIX::WTERMSIG($?));
+    } else {
+	error(_g('%s failed with unknown exit code %d'), $p, $?);
+    }
+}
+
+my $printforhelp = _g('Use --help for program usage information.');
+
+sub usageerr(@)
+{
+    my ($msg) = (shift);
+
+    $msg = sprintf($msg, @_) if (@_);
+    warn "$Dpkg::PROGNAME: $msg\n\n";
+    warn "$printforhelp\n";
+    exit(2);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Exit.pm b/third_party/dpkg-dev/scripts/Dpkg/Exit.pm
new file mode 100644
index 0000000..b967f42
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Exit.pm
@@ -0,0 +1,98 @@
+# Copyright © 2002 Adam Heath <doogie@debian.org>
+# Copyright © 2012-2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Exit;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Exporter qw(import);
+
+our @EXPORT_OK = qw(push_exit_handler pop_exit_handler run_exit_handlers);
+
+# XXX: Backwards compatibility, stop exporting on VERSION 2.00.
+## no critic (Variables::ProhibitPackageVars)
+our @handlers = ();
+## use critic
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Exit - program exit handlers
+
+=head1 DESCRIPTION
+
+The Dpkg::Exit module provides support functions to run handlers on exit.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item push_exit_handler($func)
+
+Register a code reference into the exit function handlers stack.
+
+=cut
+
+sub push_exit_handler {
+    my ($func) = shift;
+    push @handlers, $func;
+}
+
+=item pop_exit_handler()
+
+Pop the last registered exit handler from the handlers stack.
+
+=cut
+
+sub pop_exit_handler {
+    pop @handlers;
+}
+
+=item run_exit_handlers()
+
+Run the registered exit handlers.
+
+=cut
+
+sub run_exit_handlers {
+    &$_() foreach (reverse @handlers);
+}
+
+sub exit_handler {
+    run_exit_handlers();
+    exit(127);
+}
+
+$SIG{INT} = \&exit_handler;
+$SIG{HUP} = \&exit_handler;
+$SIG{QUIT} = \&exit_handler;
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New functions: push_exit_handler(), pop_exit_handler(), run_exit_handlers()
+Deprecated variable: @handlers
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/File.pm b/third_party/dpkg-dev/scripts/Dpkg/File.pm
new file mode 100644
index 0000000..adc46547
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/File.pm
@@ -0,0 +1,60 @@
+# Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2012 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::File;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Fcntl qw(:flock);
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+
+use Exporter qw(import);
+our @EXPORT = qw(file_lock file_slurp);
+
+sub file_lock($$) {
+    my ($fh, $filename) = @_;
+
+    # A strict dependency on libfile-fcntllock-perl being it an XS module,
+    # and dpkg-dev indirectly making use of it, makes building new perl
+    # package which bump the perl ABI impossible as these packages cannot
+    # be installed alongside.
+    eval 'use File::FcntlLock';
+    if ($@) {
+        warning(_g('File::FcntlLock not available; using flock which is not NFS-safe'));
+        flock($fh, LOCK_EX)
+            or syserr(_g('failed to get a write lock on %s'), $filename);
+    } else {
+        eval q{
+            my $fs = File::FcntlLock->new(l_type => F_WRLCK);
+            $fs->lock($fh, F_SETLKW)
+                or syserr(_g('failed to get a write lock on %s'), $filename);
+        }
+    }
+}
+
+sub file_slurp {
+    my ($fh) = @_;
+
+    local $/;
+    my $data = <$fh>;
+    return $data;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Gettext.pm b/third_party/dpkg-dev/scripts/Dpkg/Gettext.pm
new file mode 100644
index 0000000..14f9f5c
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Gettext.pm
@@ -0,0 +1,66 @@
+# Copied from /usr/share/perl5/Debconf/Gettext.pm
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+package Dpkg::Gettext;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+BEGIN {
+    eval 'use Locale::gettext';
+    if ($@) {
+        eval q{
+            sub _g {
+                return shift;
+            }
+            sub textdomain {
+            }
+            sub ngettext {
+                if ($_[2] == 1) {
+                    return $_[0];
+                } else {
+                    return $_[1];
+                }
+            }
+            sub P_ {
+                return ngettext(@_);
+            }
+        };
+    } else {
+        eval q{
+            sub _g {
+                return gettext(shift);
+            }
+            sub P_ {
+                return ngettext(@_);
+            }
+        };
+    }
+}
+
+use Exporter qw(import);
+our @EXPORT=qw(_g P_ textdomain ngettext);
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/IPC.pm b/third_party/dpkg-dev/scripts/Dpkg/IPC.pm
new file mode 100644
index 0000000..324c100
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/IPC.pm
@@ -0,0 +1,375 @@
+# Copyright © 2008-2009 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::IPC;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use Carp;
+use Exporter qw(import);
+our @EXPORT = qw(spawn wait_child);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::IPC - helper functions for IPC
+
+=head1 DESCRIPTION
+
+Dpkg::IPC offers helper functions to allow you to execute
+other programs in an easy, yet flexible way, while hiding
+all the gory details of IPC (Inter-Process Communication)
+from you.
+
+=head1 METHODS
+
+=over 4
+
+=item spawn
+
+Creates a child process and executes another program in it.
+The arguments are interpreted as a hash of options, specifying
+how to handle the in and output of the program to execute.
+Returns the pid of the child process (unless the wait_child
+option was given).
+
+Any error will cause the function to exit with one of the
+Dpkg::ErrorHandling functions.
+
+Options:
+
+=over 4
+
+=item exec
+
+Can be either a scalar, i.e. the name of the program to be
+executed, or an array reference, i.e. the name of the program
+plus additional arguments. Note that the program will never be
+executed via the shell, so you can't specify additional arguments
+in the scalar string and you can't use any shell facilities like
+globbing.
+
+Mandatory Option.
+
+=item from_file, to_file, error_to_file
+
+Filename as scalar. Standard input/output/error of the
+child process will be redirected to the file specified.
+
+=item from_handle, to_handle, error_to_handle
+
+Filehandle. Standard input/output/error of the child process will be
+dup'ed from the handle.
+
+=item from_pipe, to_pipe, error_to_pipe
+
+Scalar reference or object based on IO::Handle. A pipe will be opened for
+each of the two options and either the reading (C<to_pipe> and
+C<error_to_pipe>) or the writing end (C<from_pipe>) will be returned in
+the referenced scalar. Standard input/output/error of the child process
+will be dup'ed to the other ends of the pipes.
+
+=item from_string, to_string, error_to_string
+
+Scalar reference. Standard input/output/error of the child
+process will be redirected to the string given as reference. Note
+that it wouldn't be strictly necessary to use a scalar reference
+for C<from_string>, as the string is not modified in any way. This was
+chosen only for reasons of symmetry with C<to_string> and
+C<error_to_string>. C<to_string> and C<error_to_string> imply the
+C<wait_child> option.
+
+=item wait_child
+
+Scalar. If containing a true value, wait_child() will be called before
+returning. The return value of spawn() will be a true value, not the pid.
+
+=item nocheck
+
+Scalar. Option of the wait_child() call.
+
+=item timeout
+
+Scalar. Option of the wait_child() call.
+
+=item chdir
+
+Scalar. The child process will chdir in the indicated directory before
+calling exec.
+
+=item env
+
+Hash reference. The child process will populate %ENV with the items of the
+hash before calling exec. This allows exporting environment variables.
+
+=item delete_env
+
+Array reference. The child process will remove all environment variables
+listed in the array before calling exec.
+
+=back
+
+=cut
+
+sub _sanity_check_opts {
+    my (%opts) = @_;
+
+    croak 'exec parameter is mandatory in spawn()'
+	unless $opts{exec};
+
+    my $to = my $error_to = my $from = 0;
+    foreach (qw(file handle string pipe)) {
+	$to++ if $opts{"to_$_"};
+	$error_to++ if $opts{"error_to_$_"};
+	$from++ if $opts{"from_$_"};
+    }
+    croak 'not more than one of to_* parameters is allowed'
+	if $to > 1;
+    croak 'not more than one of error_to_* parameters is allowed'
+	if $error_to > 1;
+    croak 'not more than one of from_* parameters is allowed'
+	if $from > 1;
+
+    foreach (qw(to_string error_to_string from_string)) {
+	if (exists $opts{$_} and
+	    (not ref($opts{$_}) or ref($opts{$_}) ne 'SCALAR')) {
+	    croak "parameter $_ must be a scalar reference";
+	}
+    }
+
+    foreach (qw(to_pipe error_to_pipe from_pipe)) {
+	if (exists $opts{$_} and
+	    (not ref($opts{$_}) or (ref($opts{$_}) ne 'SCALAR' and
+				 not $opts{$_}->isa('IO::Handle')))) {
+	    croak "parameter $_ must be a scalar reference or " .
+	          'an IO::Handle object';
+	}
+    }
+
+    if (exists $opts{timeout} and defined($opts{timeout}) and
+        $opts{timeout} !~ /^\d+$/) {
+	croak 'parameter timeout must be an integer';
+    }
+
+    if (exists $opts{env} and ref($opts{env}) ne 'HASH') {
+	croak 'parameter env must be a hash reference';
+    }
+
+    if (exists $opts{delete_env} and ref($opts{delete_env}) ne 'ARRAY') {
+	croak 'parameter delete_env must be an array reference';
+    }
+
+    return %opts;
+}
+
+sub spawn {
+    my (%opts) = _sanity_check_opts(@_);
+    $opts{close_in_child} ||= [];
+    my @prog;
+    if (ref($opts{exec}) =~ /ARRAY/) {
+	push @prog, @{$opts{exec}};
+    } elsif (not ref($opts{exec})) {
+	push @prog, $opts{exec};
+    } else {
+	croak 'invalid exec parameter in spawn()';
+    }
+    my ($from_string_pipe, $to_string_pipe, $error_to_string_pipe);
+    if ($opts{to_string}) {
+	$opts{to_pipe} = \$to_string_pipe;
+	$opts{wait_child} = 1;
+    }
+    if ($opts{error_to_string}) {
+	$opts{error_to_pipe} = \$error_to_string_pipe;
+	$opts{wait_child} = 1;
+    }
+    if ($opts{from_string}) {
+	$opts{from_pipe} = \$from_string_pipe;
+    }
+    # Create pipes if needed
+    my ($input_pipe, $output_pipe, $error_pipe);
+    if ($opts{from_pipe}) {
+	pipe($opts{from_handle}, $input_pipe)
+	    or syserr(_g('pipe for %s'), "@prog");
+	${$opts{from_pipe}} = $input_pipe;
+	push @{$opts{close_in_child}}, $input_pipe;
+    }
+    if ($opts{to_pipe}) {
+	pipe($output_pipe, $opts{to_handle})
+	    or syserr(_g('pipe for %s'), "@prog");
+	${$opts{to_pipe}} = $output_pipe;
+	push @{$opts{close_in_child}}, $output_pipe;
+    }
+    if ($opts{error_to_pipe}) {
+	pipe($error_pipe, $opts{error_to_handle})
+	    or syserr(_g('pipe for %s'), "@prog");
+	${$opts{error_to_pipe}} = $error_pipe;
+	push @{$opts{close_in_child}}, $error_pipe;
+    }
+    # Fork and exec
+    my $pid = fork();
+    syserr(_g('cannot fork for %s'), "@prog") unless defined $pid;
+    if (not $pid) {
+	# Define environment variables
+	if ($opts{env}) {
+	    foreach (keys %{$opts{env}}) {
+		$ENV{$_} = $opts{env}{$_};
+	    }
+	}
+	if ($opts{delete_env}) {
+	    delete $ENV{$_} foreach (@{$opts{delete_env}});
+	}
+	# Change the current directory
+	if ($opts{chdir}) {
+	    chdir($opts{chdir}) or syserr(_g('chdir to %s'), $opts{chdir});
+	}
+	# Redirect STDIN if needed
+	if ($opts{from_file}) {
+	    open(STDIN, '<', $opts{from_file})
+	        or syserr(_g('cannot open %s'), $opts{from_file});
+	} elsif ($opts{from_handle}) {
+	    open(STDIN, '<&', $opts{from_handle})
+		or syserr(_g('reopen stdin'));
+	    close($opts{from_handle}); # has been duped, can be closed
+	}
+	# Redirect STDOUT if needed
+	if ($opts{to_file}) {
+	    open(STDOUT, '>', $opts{to_file})
+	        or syserr(_g('cannot write %s'), $opts{to_file});
+	} elsif ($opts{to_handle}) {
+	    open(STDOUT, '>&', $opts{to_handle})
+		or syserr(_g('reopen stdout'));
+	    close($opts{to_handle}); # has been duped, can be closed
+	}
+	# Redirect STDERR if needed
+	if ($opts{error_to_file}) {
+	    open(STDERR, '>', $opts{error_to_file})
+	        or syserr(_g('cannot write %s'), $opts{error_to_file});
+	} elsif ($opts{error_to_handle}) {
+	    open(STDERR, '>&', $opts{error_to_handle})
+	        or syserr(_g('reopen stdout'));
+	    close($opts{error_to_handle}); # has been duped, can be closed
+	}
+	# Close some inherited filehandles
+	close($_) foreach (@{$opts{close_in_child}});
+	# Execute the program
+	exec({ $prog[0] } @prog) or syserr(_g('unable to execute %s'), "@prog");
+    }
+    # Close handle that we can't use any more
+    close($opts{from_handle}) if exists $opts{from_handle};
+    close($opts{to_handle}) if exists $opts{to_handle};
+    close($opts{error_to_handle}) if exists $opts{error_to_handle};
+
+    if ($opts{from_string}) {
+	print { $from_string_pipe } ${$opts{from_string}};
+	close($from_string_pipe);
+    }
+    if ($opts{to_string}) {
+	local $/ = undef;
+	${$opts{to_string}} = readline($to_string_pipe);
+    }
+    if ($opts{error_to_string}) {
+	local $/ = undef;
+	${$opts{error_to_string}} = readline($error_to_string_pipe);
+    }
+    if ($opts{wait_child}) {
+	my $cmdline = "@prog";
+	if ($opts{env}) {
+	    foreach (keys %{$opts{env}}) {
+		$cmdline = "$_=\"" . $opts{env}{$_} . "\" $cmdline";
+	    }
+	}
+	wait_child($pid, nocheck => $opts{nocheck},
+                   timeout => $opts{timeout}, cmdline => $cmdline);
+	return 1;
+    }
+
+    return $pid;
+}
+
+
+=item wait_child
+
+Takes as first argument the pid of the process to wait for.
+Remaining arguments are taken as a hash of options. Returns
+nothing. Fails if the child has been ended by a signal or
+if it exited non-zero.
+
+Options:
+
+=over 4
+
+=item cmdline
+
+String to identify the child process in error messages.
+Defaults to "child process".
+
+=item nocheck
+
+If true do not check the return status of the child (and thus
+do not fail it it has been killed or if it exited with a
+non-zero return code).
+
+=item timeout
+
+Set a maximum time to wait for the process, after that fail
+with an error message.
+
+=back
+
+=cut
+
+sub wait_child {
+    my ($pid, %opts) = @_;
+    $opts{cmdline} ||= _g('child process');
+    croak 'no PID set, cannot wait end of process' unless $pid;
+    eval {
+        local $SIG{ALRM} = sub { die "alarm\n" };
+        alarm($opts{timeout}) if defined($opts{timeout});
+        $pid == waitpid($pid, 0) or syserr(_g('wait for %s'), $opts{cmdline});
+        alarm(0) if defined($opts{timeout});
+    };
+    if ($@) {
+        die $@ unless $@ eq "alarm\n";
+        error(ngettext("%s didn't complete in %d second",
+                       "%s didn't complete in %d seconds",
+                       $opts{timeout}),
+              $opts{cmdline}, $opts{timeout});
+    }
+    unless ($opts{nocheck}) {
+	subprocerr($opts{cmdline}) if $?;
+    }
+}
+
+1;
+__END__
+
+=back
+
+=head1 AUTHORS
+
+Written by Raphaël Hertzog <hertzog@debian.org> and
+Frank Lichtenheld <djpig@debian.org>.
+
+=head1 SEE ALSO
+
+Dpkg, Dpkg::ErrorHandling
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Index.pm b/third_party/dpkg-dev/scripts/Dpkg/Index.pm
new file mode 100644
index 0000000..057d9101
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Index.pm
@@ -0,0 +1,353 @@
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Index;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control;
+use Dpkg::Compression::FileHandle;
+
+use parent qw(Dpkg::Interface::Storable);
+
+use overload
+    '@{}' => sub { return $_[0]->{order} },
+    fallback => 1;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Index - generic index of control information
+
+=head1 DESCRIPTION
+
+This object represent a set of Dpkg::Control objects.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my $index = Dpkg::Index->new(%opts)
+
+Creates a new empty index. See set_options() for more details.
+
+=cut
+
+sub new {
+    my ($this, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+	items => {},
+	order => [],
+	get_key_func => sub { return $_[0]->{Package} },
+	type => CTRL_UNKNOWN,
+    };
+    bless $self, $class;
+    $self->set_options(%opts);
+    if (exists $opts{load}) {
+	$self->load($opts{load});
+    }
+
+    return $self;
+}
+
+=item $index->set_options(%opts)
+
+The "type" option is checked first to define default values for other
+options. Here are the relevant options: "get_key_func" is a function
+returning a key for the item passed in parameters. The index can only
+contain one item with a given key. The function used depend on the
+type: for CTRL_INFO_PKG, CTRL_INDEX_SRC, CTRL_INDEX_PKG and CTRL_PKG_DEB
+it's simply the Package field; for CTRL_PKG_SRC and CTRL_INFO_SRC, it's
+the Source field; for CTRL_CHANGELOG it's the Source and the Version
+fields (concatenated with an intermediary "_"); for CTRL_FILE_CHANGES it's
+the Source, Version and Architecture fields (concatenated with "_");
+for CTRL_FILE_VENDOR it's the Vendor field; for CTRL_FILE_STATUS it's the
+Package and Architecture fields (concatenated with "_"). Otherwise it's
+the Package field by default.
+
+=cut
+
+sub set_options {
+    my ($self, %opts) = @_;
+
+    # Default values based on type
+    if (exists $opts{type}) {
+        my $t = $opts{type};
+        if ($t == CTRL_INFO_PKG or $t == CTRL_INDEX_SRC or
+	         $t == CTRL_INDEX_PKG or $t == CTRL_PKG_DEB) {
+	    $self->{get_key_func} = sub { return $_[0]->{Package}; };
+        } elsif ($t == CTRL_PKG_SRC or $t == CTRL_INFO_SRC) {
+	    $self->{get_key_func} = sub { return $_[0]->{Source}; };
+        } elsif ($t == CTRL_CHANGELOG) {
+	    $self->{get_key_func} = sub {
+		return $_[0]->{Source} . '_' . $_[0]->{Version};
+	    };
+        } elsif ($t == CTRL_FILE_CHANGES) {
+	    $self->{get_key_func} = sub {
+		return $_[0]->{Source} . '_' . $_[0]->{Version} . '_' .
+		       $_[0]->{Architecture};
+	    };
+        } elsif ($t == CTRL_FILE_VENDOR) {
+	    $self->{get_key_func} = sub { return $_[0]->{Vendor}; };
+        } elsif ($t == CTRL_FILE_STATUS) {
+	    $self->{get_key_func} = sub {
+		return $_[0]->{Package} . '_' . $_[0]->{Architecture};
+	    };
+        }
+    }
+
+    # Options set by the user override default values
+    $self->{$_} = $opts{$_} foreach keys %opts;
+}
+
+=item $index->get_type()
+
+Returns the type of control information stored. See the type parameter
+set during new().
+
+=cut
+
+sub get_type {
+    my ($self) = @_;
+    return $self->{type};
+}
+
+=item $index->add($item, [$key])
+
+Add a new item in the index. If the $key parameter is omitted, the key
+will be generated with the get_key_func function (see set_options() for
+details).
+
+=cut
+
+sub add {
+    my ($self, $item, $key) = @_;
+    unless (defined $key) {
+	$key = $self->{get_key_func}($item);
+    }
+    if (not exists $self->{items}{$key}) {
+	push @{$self->{order}}, $key;
+    }
+    $self->{items}{$key} = $item;
+}
+
+=item $index->load($file)
+
+Reads the file and creates all items parsed. Returns the number of items
+parsed. Handles compressed files transparently based on their extensions.
+
+=item $index->parse($fh, $desc)
+
+Reads the filehandle and creates all items parsed. Returns the number of
+items parsed.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $desc) = @_;
+    my $item = $self->new_item();
+    my $i = 0;
+    while ($item->parse($fh, $desc)) {
+	$self->add($item);
+	$item = $self->new_item();
+	$i++;
+    }
+    return $i;
+}
+
+=item $index->save($file)
+
+Writes the content of the index in a file. Auto-compresses files
+based on their extensions.
+
+=item my $item = $index->new_item()
+
+Creates a new item. Mainly useful for derived objects that would want
+to override this method to return something else than a Dpkg::Control
+object.
+
+=cut
+
+sub new_item {
+    my ($self) = @_;
+    return Dpkg::Control->new(type => $self->{type});
+}
+
+=item my $item = $index->get_by_key($key)
+
+Returns the item identified by $key or undef.
+
+=cut
+
+sub get_by_key {
+    my ($self, $key) = @_;
+    return $self->{items}{$key} if exists $self->{items}{$key};
+    return;
+}
+
+=item my @keys = $index->get_keys(%criteria)
+
+Returns the keys of items that matches all the criteria. The key of the
+%criteria hash is a field name and the value is either a regex that needs
+to match the field value, or a reference to a function that must return
+true and that receives the field value as single parameter, or a scalar
+that must be equal to the field value.
+
+=cut
+
+sub get_keys {
+    my ($self, %crit) = @_;
+    my @selected = @{$self->{order}};
+    foreach my $s_crit (keys %crit) { # search criteria
+	if (ref($crit{$s_crit}) eq 'Regexp') {
+	    @selected = grep {
+		$self->{items}{$_}{$s_crit} =~ $crit{$s_crit}
+	    } @selected;
+	} elsif (ref($crit{$s_crit}) eq 'CODE') {
+	    @selected = grep {
+		&{$crit{$s_crit}}($self->{items}{$_}{$s_crit});
+	    } @selected;
+	} else {
+	    @selected = grep {
+		$self->{items}{$_}{$s_crit} eq $crit{$s_crit}
+	    } @selected;
+	}
+    }
+    return @selected;
+}
+
+=item my @items = $index->get(%criteria)
+
+Returns all the items that matches all the criteria.
+
+=cut
+
+sub get {
+    my ($self, %crit) = @_;
+    return map { $self->{items}{$_} } $self->get_keys(%crit);
+}
+
+=item $index->remove_by_key($key)
+
+Remove the item identified by the given key.
+
+=cut
+
+sub remove_by_key {
+    my ($self, $key) = @_;
+    @{$self->{order}} = grep { $_ ne $key } @{$self->{order}};
+    return delete $self->{items}{$key};
+}
+
+=item my @items = $index->remove(%criteria)
+
+Returns and removes all the items that matches all the criteria.
+
+=cut
+
+sub remove {
+    my ($self, %crit) = @_;
+    my @keys = $self->get_keys(%crit);
+    my (%keys, @ret);
+    foreach my $key (@keys) {
+	$keys{$key} = 1;
+	push @ret, $self->{items}{$key} if defined wantarray;
+	delete $self->{items}{$key};
+    }
+    @{$self->{order}} = grep { not exists $keys{$_} } @{$self->{order}};
+    return @ret;
+}
+
+=item $index->merge($other_index, %opts)
+
+Merge the entries of the other index. While merging, the keys of the merged
+index are used, they are not re-computed (unless you have set the options
+"keep_keys" to "0"). It's your responsibility to ensure that they have been
+computed with the same function.
+
+=cut
+
+sub merge {
+    my ($self, $other, %opts) = @_;
+    $opts{keep_keys} = 1 unless exists $opts{keep_keys};
+    foreach my $key ($other->get_keys()) {
+	$self->add($other->get_by_key($key), $opts{keep_keys} ? $key : undef);
+    }
+}
+
+=item $index->sort(\&sortfunc)
+
+Sort the index with the given sort function. If no function is given, an
+alphabetic sort is done based on the keys. The sort function receives the
+items themselves as parameters and not the keys.
+
+=cut
+
+sub sort {
+    my ($self, $func) = @_;
+    if (defined $func) {
+	@{$self->{order}} = sort {
+	    &$func($self->{items}{$a}, $self->{items}{$b})
+	} @{$self->{order}};
+    } else {
+	@{$self->{order}} = sort @{$self->{order}};
+    }
+}
+
+=item my $str = $index->output()
+
+=item "$index"
+
+Get a string representation of the index. The Dpkg::Control objects are
+output in the order which they have been read or added except if the order
+hae been changed with sort().
+
+=item $index->output($fh)
+
+Print the string representation of the index to a filehandle.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str = '';
+    foreach my $key ($self->get_keys()) {
+	if (defined $fh) {
+	    print { $fh } $self->get_by_key($key) . "\n";
+	}
+	if (defined wantarray) {
+	    $str .= $self->get_by_key($key) . "\n";
+	}
+    }
+    return $str;
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Interface/Storable.pm b/third_party/dpkg-dev/scripts/Dpkg/Interface/Storable.pm
new file mode 100644
index 0000000..adef210
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Interface/Storable.pm
@@ -0,0 +1,147 @@
+# Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Interface::Storable;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.00';
+
+use Carp;
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Compression::FileHandle;
+
+use overload
+    '""' => \&_stringify,
+    'fallback' => 1;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Interface::Storable - common methods related to object serialization
+
+=head1 DESCRIPTION
+
+Dpkg::Interface::Storable is only meant to be used as parent
+class for other objects. It provides common methods that are
+all implemented on top of two basic methods parse() and output().
+
+=head1 BASE METHODS
+
+Those methods must be provided by the object that wish to inherit
+from Dpkg::Interface::Storable so that the methods provided can work.
+
+=over 4
+
+=item $obj->parse($fh, $desc)
+
+This methods initialize the object with the data stored in the
+filehandle. $desc is optional and is a textual description of
+the filehandle used in error messages.
+
+=item $string = $obj->output($fh)
+
+This method returns a string representation of the object in $string
+and it writes the same string to $fh (if it's defined).
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item $obj->load($filename)
+
+Initialize the object with the data stored in the file. The file can be
+compressed, it will be uncompressed on the fly by using a
+Dpkg::Compression::FileHandle object. If $filename is "-", then the
+standard input is read (no compression is allowed in that case).
+
+=cut
+
+sub load {
+    my ($self, $file, @options) = @_;
+    unless ($self->can('parse')) {
+	croak ref($self) . ' cannot be loaded, it lacks the parse method';
+    }
+    my ($desc, $fh) = ($file, undef);
+    if ($file eq '-') {
+	$fh = \*STDIN;
+	$desc = _g('<standard input>');
+    } else {
+	$fh = Dpkg::Compression::FileHandle->new();
+	open($fh, '<', $file) or syserr(_g('cannot read %s'), $file);
+    }
+    my $res = $self->parse($fh, $desc, @options);
+    if ($file ne '-') {
+	close($fh) or syserr(_g('cannot close %s'), $file);
+    }
+    return $res;
+}
+
+=item $obj->save($filename)
+
+Store the object in the file. If the filename ends with a known
+compression extension, it will be compressed on the fly by using a
+Dpkg::Compression::FileHandle object. If $filename is "-", then the
+standard output is used (data are written uncompressed in that case).
+
+=cut
+
+sub save {
+    my ($self, $file, @options) = @_;
+    unless ($self->can('output')) {
+	croak ref($self) . ' cannot be saved, it lacks the output method';
+    }
+    my $fh;
+    if ($file eq '-') {
+	$fh = \*STDOUT;
+    } else {
+	$fh = Dpkg::Compression::FileHandle->new();
+	open($fh, '>', $file) or syserr(_g('cannot write %s'), $file);
+    }
+    $self->output($fh, @options);
+    if ($file ne '-') {
+	close($fh) or syserr(_g('cannot close %s'), $file);
+    }
+}
+
+=item "$obj"
+
+Return a string representation of the object.
+
+=cut
+
+sub _stringify {
+    my ($self) = @_;
+    unless ($self->can('output')) {
+	croak ref($self) . ' cannot be stringified, it lacks the output method';
+    }
+    return $self->output();
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Package.pm b/third_party/dpkg-dev/scripts/Dpkg/Package.pm
new file mode 100644
index 0000000..04fbb00d
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Package.pm
@@ -0,0 +1,45 @@
+# Copyright © 2006 Frank Lichtenheld <djpig@debian.org>
+# Copyright © 2007,2012 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Package;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::Gettext;
+
+use Exporter qw(import);
+our @EXPORT = qw(pkg_name_is_illegal);
+
+sub pkg_name_is_illegal($) {
+    my $name = shift || '';
+
+    if ($name eq '') {
+        return _g('may not be empty string');
+    }
+    if ($name =~ m/[^-+.0-9a-z]/o) {
+        return sprintf(_g("character '%s' not allowed"), $&);
+    }
+    if ($name !~ m/^[0-9a-z]/o) {
+        return _g('must start with an alphanumeric character');
+    }
+
+    return;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Path.pm b/third_party/dpkg-dev/scripts/Dpkg/Path.pm
new file mode 100644
index 0000000..969b1d6c
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Path.pm
@@ -0,0 +1,295 @@
+# Copyright © 2007-2011 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2011 Linaro Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Path;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+use Exporter qw(import);
+use File::Spec;
+use Cwd qw(realpath);
+
+use Dpkg::Arch qw(get_host_arch debarch_to_debtriplet);
+use Dpkg::IPC;
+
+our @EXPORT_OK = qw(get_pkg_root_dir relative_to_pkg_root
+		    guess_pkg_root_dir check_files_are_the_same
+		    resolve_symlink canonpath find_command
+		    get_control_path find_build_file);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Path - some common path handling functions
+
+=head1 DESCRIPTION
+
+It provides some functions to handle various path.
+
+=head1 METHODS
+
+=over 8
+
+=item get_pkg_root_dir($file)
+
+This function will scan upwards the hierarchy of directory to find out
+the directory which contains the "DEBIAN" sub-directory and it will return
+its path. This directory is the root directory of a package being built.
+
+If no DEBIAN subdirectory is found, it will return undef.
+
+=cut
+
+sub get_pkg_root_dir($) {
+    my $file = shift;
+    $file =~ s{/+$}{};
+    $file =~ s{/+[^/]+$}{} if not -d $file;
+    while ($file) {
+	return $file if -d "$file/DEBIAN";
+	last if $file !~ m{/};
+	$file =~ s{/+[^/]+$}{};
+    }
+    return;
+}
+
+=item relative_to_pkg_root($file)
+
+Returns the filename relative to get_pkg_root_dir($file).
+
+=cut
+
+sub relative_to_pkg_root($) {
+    my $file = shift;
+    my $pkg_root = get_pkg_root_dir($file);
+    if (defined $pkg_root) {
+	$pkg_root .= '/';
+	return $file if ($file =~ s/^\Q$pkg_root\E//);
+    }
+    return;
+}
+
+=item guess_pkg_root_dir($file)
+
+This function tries to guess the root directory of the package build tree.
+It will first use get_pkg_root_dir(), but it will fallback to a more
+imprecise check: namely it will use the parent directory that is a
+sub-directory of the debian directory.
+
+It can still return undef if a file outside of the debian sub-directory is
+provided.
+
+=cut
+
+sub guess_pkg_root_dir($) {
+    my $file = shift;
+    my $root = get_pkg_root_dir($file);
+    return $root if defined $root;
+
+    $file =~ s{/+$}{};
+    $file =~ s{/+[^/]+$}{} if not -d $file;
+    my $parent = $file;
+    while ($file) {
+	$parent =~ s{/+[^/]+$}{};
+	last if not -d $parent;
+	return $file if check_files_are_the_same('debian', $parent);
+	$file = $parent;
+	last if $file !~ m{/};
+    }
+    return;
+}
+
+=item check_files_are_the_same($file1, $file2, $resolve_symlink)
+
+This function verifies that both files are the same by checking that the device
+numbers and the inode numbers returned by stat()/lstat() are the same. If
+$resolve_symlink is true then stat() is used, otherwise lstat() is used.
+
+=cut
+
+sub check_files_are_the_same($$;$) {
+    my ($file1, $file2, $resolve_symlink) = @_;
+    return 0 if ((! -e $file1) || (! -e $file2));
+    my (@stat1, @stat2);
+    if ($resolve_symlink) {
+        @stat1 = stat($file1);
+        @stat2 = stat($file2);
+    } else {
+        @stat1 = lstat($file1);
+        @stat2 = lstat($file2);
+    }
+    my $result = ($stat1[0] == $stat2[0]) && ($stat1[1] == $stat2[1]);
+    return $result;
+}
+
+
+=item canonpath($file)
+
+This function returns a cleaned path. It simplifies double //, and remove
+/./ and /../ intelligently. For /../ it simplifies the path only if the
+previous element is not a symlink. Thus it should only be used on real
+filenames.
+
+=cut
+
+sub canonpath($) {
+    my $path = shift;
+    $path = File::Spec->canonpath($path);
+    my ($v, $dirs, $file) = File::Spec->splitpath($path);
+    my @dirs = File::Spec->splitdir($dirs);
+    my @new;
+    foreach my $d (@dirs) {
+	if ($d eq '..') {
+	    if (scalar(@new) > 0 and $new[-1] ne '..') {
+		next if $new[-1] eq ''; # Root directory has no parent
+		my $parent = File::Spec->catpath($v,
+			File::Spec->catdir(@new), '');
+		if (not -l $parent) {
+		    pop @new;
+		} else {
+		    push @new, $d;
+		}
+	    } else {
+		push @new, $d;
+	    }
+	} else {
+	    push @new, $d;
+	}
+    }
+    return File::Spec->catpath($v, File::Spec->catdir(@new), $file);
+}
+
+=item $newpath = resolve_symlink($symlink)
+
+Return the filename of the file pointed by the symlink. The new name is
+canonicalized by canonpath().
+
+=cut
+
+sub resolve_symlink($) {
+    my $symlink = shift;
+    my $content = readlink($symlink);
+    return unless defined $content;
+    if (File::Spec->file_name_is_absolute($content)) {
+	return canonpath($content);
+    } else {
+	my ($link_v, $link_d, $link_f) = File::Spec->splitpath($symlink);
+	my ($cont_v, $cont_d, $cont_f) = File::Spec->splitpath($content);
+	my $new = File::Spec->catpath($link_v, $link_d . '/' . $cont_d, $cont_f);
+	return canonpath($new);
+    }
+}
+
+
+=item my $cmdpath = find_command($command)
+
+Return the path of the command if available on an absolute or relative
+path or on the $PATH, undef otherwise.
+
+=cut
+
+sub find_command($) {
+    my $cmd = shift;
+
+    if ($cmd =~ m{/}) {
+	return "$cmd" if -x "$cmd";
+    } else {
+	foreach my $dir (split(/:/, $ENV{PATH})) {
+	    return "$dir/$cmd" if -x "$dir/$cmd";
+	}
+    }
+    return;
+}
+
+=item my $control_file = get_control_path($pkg, $filetype)
+
+Return the path of the control file of type $filetype for the given
+package.
+
+=item my @control_files = get_control_path($pkg)
+
+Return the path of all available control files for the given package.
+
+=cut
+
+sub get_control_path($;$) {
+    my ($pkg, $filetype) = @_;
+    my $control_file;
+    my @exec = ('dpkg-query', '--control-path', $pkg);
+    push @exec, $filetype if defined $filetype;
+    spawn(exec => \@exec, wait_child => 1, to_string => \$control_file);
+    chomp($control_file);
+    if (defined $filetype) {
+	return if $control_file eq '';
+	return $control_file;
+    }
+    return () if $control_file eq '';
+    return split(/\n/, $control_file);
+}
+
+=item my $file = find_build_file($basename)
+
+Selects the right variant of the given file: the arch-specific variant
+("$basename.$arch") has priority over the OS-specific variant
+("$basename.$os") which has priority over the default variant
+("$basename"). If none of the files exists, then it returns undef.
+
+=item my @files = find_build_file($basename)
+
+Return the available variants of the given file. Returns an empty
+list if none of the files exists.
+
+=cut
+
+sub find_build_file($) {
+    my $base = shift;
+    my $host_arch = get_host_arch();
+    my ($abi, $host_os, $cpu) = debarch_to_debtriplet($host_arch);
+    my @files;
+    foreach my $f ("$base.$host_arch", "$base.$host_os", "$base") {
+        push @files, $f if -f $f;
+    }
+    return @files if wantarray;
+    return $files[0] if scalar @files;
+    return;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.03
+
+New function: find_build_file()
+
+=head2 Version 1.02
+
+New function: get_control_path()
+
+=head2 Version 1.01
+
+New function: find_command()
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Shlibs.pm b/third_party/dpkg-dev/scripts/Dpkg/Shlibs.pm
new file mode 100644
index 0000000..d7c399a9
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Shlibs.pm
@@ -0,0 +1,149 @@
+# Copyright © 2007 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Shlibs;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.02';
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(add_library_dir get_library_paths reset_library_paths
+                    find_library);
+
+
+use File::Spec;
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Shlibs::Objdump;
+use Dpkg::Util qw(:list);
+use Dpkg::Path qw(resolve_symlink canonpath);
+use Dpkg::Arch qw(debarch_to_gnutriplet get_build_arch get_host_arch
+                  gnutriplet_to_multiarch debarch_to_multiarch);
+
+use constant DEFAULT_LIBRARY_PATH =>
+    qw(/lib /usr/lib /lib32 /usr/lib32 /lib64 /usr/lib64
+       /emul/ia32-linux/lib /emul/ia32-linux/usr/lib);
+
+# Adjust set of directories to consider when we're in a situation of a
+# cross-build or a build of a cross-compiler
+my @crosslibrarypaths;
+my ($crossprefix, $multiarch);
+# Detect cross compiler builds
+if ($ENV{GCC_TARGET}) {
+    $crossprefix = debarch_to_gnutriplet($ENV{GCC_TARGET});
+    $multiarch = debarch_to_multiarch($ENV{GCC_TARGET});
+}
+if ($ENV{DEB_TARGET_GNU_TYPE} and
+    ($ENV{DEB_TARGET_GNU_TYPE} ne $ENV{DEB_BUILD_GNU_TYPE}))
+{
+    $crossprefix = $ENV{DEB_TARGET_GNU_TYPE};
+    $multiarch = gnutriplet_to_multiarch($ENV{DEB_TARGET_GNU_TYPE});
+}
+# host for normal cross builds.
+if (get_build_arch() ne get_host_arch()) {
+    $crossprefix = debarch_to_gnutriplet(get_host_arch());
+    $multiarch = debarch_to_multiarch(get_host_arch());
+}
+# Define list of directories containing crossbuilt libraries
+if ($crossprefix) {
+    push @crosslibrarypaths, "/lib/$multiarch", "/usr/lib/$multiarch",
+            "/$crossprefix/lib", "/usr/$crossprefix/lib",
+            "/$crossprefix/lib32", "/usr/$crossprefix/lib32",
+            "/$crossprefix/lib64", "/usr/$crossprefix/lib64";
+}
+
+my @librarypaths = (DEFAULT_LIBRARY_PATH, @crosslibrarypaths);
+
+# XXX: Deprecated. Update library paths with LD_LIBRARY_PATH
+if ($ENV{LD_LIBRARY_PATH}) {
+    foreach my $path (reverse split( /:/, $ENV{LD_LIBRARY_PATH} )) {
+	$path =~ s{/+$}{};
+	add_library_dir($path);
+    }
+}
+
+# Update library paths with ld.so config
+parse_ldso_conf('/etc/ld.so.conf') if -e '/etc/ld.so.conf';
+
+my %visited;
+sub parse_ldso_conf {
+    my $file = shift;
+    open my $fh, '<', $file or syserr(_g('cannot open %s'), $file);
+    $visited{$file}++;
+    while (<$fh>) {
+	next if /^\s*$/;
+	chomp;
+	s{/+$}{};
+	if (/^include\s+(\S.*\S)\s*$/) {
+	    foreach my $include (glob($1)) {
+		parse_ldso_conf($include) if -e $include
+		    && !$visited{$include};
+	    }
+	} elsif (m{^\s*/}) {
+	    s/^\s+//;
+	    my $libdir = $_;
+	    if (none { $_ eq $libdir } @librarypaths) {
+		push @librarypaths, $libdir;
+	    }
+	}
+    }
+    close $fh;
+}
+
+sub add_library_dir {
+    my ($dir) = @_;
+    unshift @librarypaths, $dir;
+}
+
+sub get_library_paths {
+    return @librarypaths;
+}
+
+sub reset_library_paths {
+    @librarypaths = ();
+}
+
+# find_library ($soname, \@rpath, $format, $root)
+sub find_library {
+    my ($lib, $rpath, $format, $root) = @_;
+    $root //= '';
+    $root =~ s{/+$}{};
+    my @rpath = @{$rpath};
+    foreach my $dir (@rpath, @librarypaths) {
+	my $checkdir = "$root$dir";
+	# If the directory checked is a symlink, check if it doesn't
+	# resolve to another public directory (which is then the canonical
+	# directory to use instead of this one). Typical example
+	# is /usr/lib64 -> /usr/lib on amd64.
+	if (-l $checkdir) {
+	    my $newdir = resolve_symlink($checkdir);
+	    if (any { "$root$_" eq "$newdir" } (@rpath, @librarypaths)) {
+		$checkdir = $newdir;
+	    }
+	}
+	if (-e "$checkdir/$lib") {
+	    my $libformat = Dpkg::Shlibs::Objdump::get_format("$checkdir/$lib");
+	    if ($format eq $libformat) {
+		return canonpath("$checkdir/$lib");
+	    }
+	}
+    }
+    return;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Cppfilt.pm b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Cppfilt.pm
new file mode 100644
index 0000000..78beb67
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Cppfilt.pm
@@ -0,0 +1,113 @@
+# Copyright © 2009-2010 Modestas Vainius <modax@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Shlibs::Cppfilt;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Exporter qw(import);
+
+use Dpkg::ErrorHandling;
+use Dpkg::IPC;
+use IO::Handle;
+
+our @EXPORT = qw(cppfilt_demangle_cpp);
+our @EXPORT_OK = qw(cppfilt_demangle);
+
+# A hash of 'objects' referring to preforked c++filt processes for the distinct
+# demangling types.
+my %cppfilts;
+
+sub get_cppfilt {
+    my $type = shift || 'auto';
+
+    # Fork c++filt process for demangling $type unless it is forked already.
+    # Keeping c++filt running improves performance a lot.
+    my $filt;
+    if (exists $cppfilts{$type}) {
+	$filt = $cppfilts{$type};
+    } else {
+	$filt = { from => undef, to => undef,
+	            last_symbol => '', last_result => '' };
+	$filt->{pid} = spawn(exec => [ 'c++filt', "--format=$type" ],
+	                     from_pipe => \$filt->{from},
+	                     to_pipe => \$filt->{to});
+	syserr(_g('unable to execute %s'), 'c++filt')
+	    unless defined $filt->{from};
+	$filt->{from}->autoflush(1);
+
+	$cppfilts{$type} = $filt;
+    }
+    return $filt;
+}
+
+# Demangle the given $symbol using demangler for the specified $type (defaults
+# to 'auto') . Extraneous characters trailing after a mangled name are kept
+# intact. If neither whole $symbol nor portion of it could be demangled, undef
+# is returned.
+sub cppfilt_demangle {
+    my ($symbol, $type) = @_;
+
+    # Start or get c++filt 'object' for the requested type.
+    my $filt = get_cppfilt($type);
+
+    # Remember the last result. Such a local optimization is cheap and useful
+    # when sequential pattern matching is performed.
+    if ($filt->{last_symbol} ne $symbol) {
+	# This write/read operation should not deadlock because c++filt flushes
+	# output buffer on LF or each invalid character.
+	print { $filt->{from} } $symbol, "\n";
+	my $demangled = readline($filt->{to});
+	chop $demangled;
+
+	# If the symbol was not demangled, return undef
+	$demangled = undef if $symbol eq $demangled;
+
+	# Remember the last result
+	$filt->{last_symbol} = $symbol;
+	$filt->{last_result} = $demangled;
+    }
+    return $filt->{last_result};
+}
+
+sub cppfilt_demangle_cpp {
+    my $symbol = shift;
+    return cppfilt_demangle($symbol, 'auto');
+}
+
+sub terminate_cppfilts {
+    foreach (keys %cppfilts) {
+	next if not defined $cppfilts{$_}{pid};
+	close $cppfilts{$_}{from};
+	close $cppfilts{$_}{to};
+	wait_child($cppfilts{$_}{pid}, cmdline => 'c++filt',
+	                               nocheck => 1,
+	                               timeout => 5);
+	delete $cppfilts{$_};
+    }
+}
+
+# Close/terminate running c++filt process(es)
+END {
+    # Make sure exitcode is not changed (by wait_child)
+    my $exitcode = $?;
+    terminate_cppfilts();
+    $? = $exitcode;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Objdump.pm b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Objdump.pm
new file mode 100644
index 0000000..ea3af69
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Objdump.pm
@@ -0,0 +1,475 @@
+# Copyright © 2007-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Shlibs::Objdump;
+
+use strict;
+use warnings;
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Path qw(find_command);
+use Dpkg::Arch qw(debarch_to_gnutriplet get_build_arch get_host_arch);
+use Dpkg::IPC;
+
+our $VERSION = '0.01';
+
+# Decide which objdump to call
+our $OBJDUMP = 'objdump';
+if (get_build_arch() ne get_host_arch()) {
+    my $od = debarch_to_gnutriplet(get_host_arch()) . '-objdump';
+    $OBJDUMP = $od if find_command($od);
+}
+
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = { objects => {} };
+    bless $self, $class;
+    return $self;
+}
+
+sub add_object {
+    my ($self, $obj) = @_;
+    my $id = $obj->get_id;
+    if ($id) {
+	$self->{objects}{$id} = $obj;
+    }
+    return $id;
+}
+
+sub analyze {
+    my ($self, $file) = @_;
+    my $obj = Dpkg::Shlibs::Objdump::Object->new($file);
+
+    return $self->add_object($obj);
+}
+
+sub locate_symbol {
+    my ($self, $name) = @_;
+    foreach my $obj (values %{$self->{objects}}) {
+	my $sym = $obj->get_symbol($name);
+	if (defined($sym) && $sym->{defined}) {
+	    return $sym;
+	}
+    }
+    return;
+}
+
+sub get_object {
+    my ($self, $objid) = @_;
+    if ($self->has_object($objid)) {
+	return $self->{objects}{$objid};
+    }
+    return;
+}
+
+sub has_object {
+    my ($self, $objid) = @_;
+    return exists $self->{objects}{$objid};
+}
+
+sub is_armhf {
+    my ($file) = @_;
+    my ($output, %opts, $pid, $res);
+    my $hf = 0;
+    my $sf = 0;
+    $pid = spawn(exec => [ "readelf", "-h", "--", $file ],
+		 env => { "LC_ALL" => "C" },
+		 to_pipe => \$output, %opts);
+    while (<$output>) {
+	chomp;
+	if (/0x5000402/) {
+	    $hf = 1;
+	    last;
+	}
+	if (/0x5000202/) {
+	    $sf = 1;
+	    last;
+	}
+    }
+    close($output);
+    wait_child($pid, nocheck => 1);
+    if ($?) {
+	subprocerr("readelf");
+    }
+    if(($hf) || ($sf)) {
+	return $hf;
+    }
+    undef $output;
+    $pid = spawn(exec => [ "readelf", "-A", "--", $file ],
+		 env => { "LC_ALL" => "C" },
+		 to_pipe => \$output, %opts);
+    while (<$output>) {
+	chomp;
+	if (/Tag_ABI_VFP_args: VFP registers/) {
+	    $hf = 1;
+	    last;
+	}
+    }
+    close($output);
+    wait_child($pid, nocheck => 1);
+    if ($?) {
+	subprocerr("readelf");
+    }
+    return $hf;
+}
+
+{
+    my %format; # Cache of result
+    sub get_format {
+	my ($file, $objdump) = @_;
+
+	$objdump //= $OBJDUMP;
+
+	if (exists $format{$file}) {
+	    return $format{$file};
+	} else {
+	    my ($output, %opts, $pid, $res);
+	    if ($objdump ne 'objdump') {
+		$opts{error_to_file} = '/dev/null';
+	    }
+	    $pid = spawn(exec => [ $objdump, '-a', '--', $file ],
+			 env => { LC_ALL => 'C' },
+			 to_pipe => \$output, %opts);
+	    while (<$output>) {
+		chomp;
+		if (/^\s*\S+:\s*file\s+format\s+(\S+)\s*$/) {
+		    $format{$file} = $1;
+		    $res = $format{$file};
+		    last;
+		}
+	    }
+	    close($output);
+	    wait_child($pid, nocheck => 1);
+	    if ($?) {
+		subprocerr('objdump') if $objdump eq 'objdump';
+		$res = get_format($file, 'objdump');
+	    }
+	    if ($res eq "elf32-littlearm") {
+		if (is_armhf($file)) {
+		    $res = "elf32-littlearm-hfabi";
+		} else {
+		    $res = "elf32-littlearm-sfabi";
+		}
+		$format{$file} = $res;
+	    }
+	    return $res;
+	}
+    }
+}
+
+sub is_elf {
+    my ($file) = @_;
+    open(my $file_fh, '<', $file) or syserr(_g('cannot read %s'), $file);
+    my ($header, $result) = ('', 0);
+    if (read($file_fh, $header, 4) == 4) {
+	$result = 1 if ($header =~ /^\177ELF$/);
+    }
+    close($file_fh);
+    return $result;
+}
+
+package Dpkg::Shlibs::Objdump::Object;
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+
+sub new {
+    my $this = shift;
+    my $file = shift || '';
+    my $class = ref($this) || $this;
+    my $self = {};
+    bless $self, $class;
+
+    $self->reset;
+    if ($file) {
+	$self->analyze($file);
+    }
+
+    return $self;
+}
+
+sub reset {
+    my ($self) = @_;
+
+    $self->{file} = '';
+    $self->{id} = '';
+    $self->{SONAME} = '';
+    $self->{HASH} = '';
+    $self->{GNU_HASH} = '';
+    $self->{SONAME} = '';
+    $self->{NEEDED} = [];
+    $self->{RPATH} = [];
+    $self->{dynsyms} = {};
+    $self->{flags} = {};
+    $self->{dynrelocs} = {};
+
+    return $self;
+}
+
+
+sub analyze {
+    my ($self, $file) = @_;
+
+    $file ||= $self->{file};
+    return unless $file;
+
+    $self->reset;
+    $self->{file} = $file;
+
+    local $ENV{LC_ALL} = 'C';
+    open(my $objdump, '-|', $OBJDUMP, '-w', '-f', '-p', '-T', '-R', $file)
+        or syserr(_g('cannot fork for %s'), $OBJDUMP);
+    my $ret = $self->parse_objdump_output($objdump);
+    close($objdump);
+    return $ret;
+}
+
+sub parse_objdump_output {
+    my ($self, $fh) = @_;
+
+    my $section = 'none';
+    while (defined($_ = <$fh>)) {
+	chomp;
+	next if /^\s*$/;
+
+	if (/^DYNAMIC SYMBOL TABLE:/) {
+	    $section = 'dynsym';
+	    next;
+	} elsif (/^DYNAMIC RELOCATION RECORDS/) {
+	    $section = 'dynreloc';
+	    $_ = <$fh>; # Skip header
+	    next;
+	} elsif (/^Dynamic Section:/) {
+	    $section = 'dyninfo';
+	    next;
+	} elsif (/^Program Header:/) {
+	    $section = 'header';
+	    next;
+	} elsif (/^Version definitions:/) {
+	    $section = 'verdef';
+	    next;
+	} elsif (/^Version References:/) {
+	    $section = 'verref';
+	    next;
+	}
+
+	if ($section eq 'dynsym') {
+	    $self->parse_dynamic_symbol($_);
+	} elsif ($section eq 'dynreloc') {
+	    if (/^\S+\s+(\S+)\s+(\S+)\s*$/) {
+		$self->{dynrelocs}{$2} = $1;
+	    } else {
+		warning(_g("couldn't parse dynamic relocation record: %s"), $_);
+	    }
+	} elsif ($section eq 'dyninfo') {
+	    if (/^\s*NEEDED\s+(\S+)/) {
+		push @{$self->{NEEDED}}, $1;
+	    } elsif (/^\s*SONAME\s+(\S+)/) {
+		$self->{SONAME} = $1;
+	    } elsif (/^\s*HASH\s+(\S+)/) {
+		$self->{HASH} = $1;
+	    } elsif (/^\s*GNU_HASH\s+(\S+)/) {
+		$self->{GNU_HASH} = $1;
+	    } elsif (/^\s*RUNPATH\s+(\S+)/) {
+                # RUNPATH takes precedence over RPATH but is
+                # considered after LD_LIBRARY_PATH while RPATH
+                # is considered before (if RUNPATH is not set).
+                $self->{RPATH} = [ split (/:/, $1) ];
+	    } elsif (/^\s*RPATH\s+(\S+)/) {
+                unless (scalar(@{$self->{RPATH}})) {
+                    $self->{RPATH} = [ split (/:/, $1) ];
+                }
+	    }
+	} elsif ($section eq 'none') {
+	    if (/^\s*.+:\s*file\s+format\s+(\S+)\s*$/) {
+		$self->{format} = $1;
+		if (($self->{format} eq "elf32-littlearm") && $self->{file}) {
+		    if (Dpkg::Shlibs::Objdump::is_armhf($self->{file})) {
+			$self->{format} = "elf32-littlearm-hfabi";
+		    } else {
+			$self->{format} = "elf32-littlearm-sfabi";
+		    }
+		}
+	    } elsif (/^architecture:\s*\S+,\s*flags\s*\S+:\s*$/) {
+		# Parse 2 lines of "-f"
+		# architecture: i386, flags 0x00000112:
+		# EXEC_P, HAS_SYMS, D_PAGED
+		# start address 0x08049b50
+		$_ = <$fh>;
+		chomp;
+		$self->{flags}{$_} = 1 foreach (split(/,\s*/));
+	    }
+	}
+    }
+    # Update status of dynamic symbols given the relocations that have
+    # been parsed after the symbols...
+    $self->apply_relocations();
+
+    return $section ne 'none';
+}
+
+# Output format of objdump -w -T
+#
+# /lib/libc.so.6:     file format elf32-i386
+#
+# DYNAMIC SYMBOL TABLE:
+# 00056ef0 g    DF .text  000000db  GLIBC_2.2   getwchar
+# 00000000 g    DO *ABS*  00000000  GCC_3.0     GCC_3.0
+# 00069960  w   DF .text  0000001e  GLIBC_2.0   bcmp
+# 00000000  w   D  *UND*  00000000              _pthread_cleanup_pop_restore
+# 0000b788 g    DF .text  0000008e  Base        .protected xine_close
+# 0000b788 g    DF .text  0000008e              .hidden IA__g_free
+# |        ||||||| |      |         |           |
+# |        ||||||| |      |         Version str (.visibility) + Symbol name
+# |        ||||||| |      Alignment
+# |        ||||||| Section name (or *UND* for an undefined symbol)
+# |        ||||||F=Function,f=file,O=object
+# |        |||||d=debugging,D=dynamic
+# |        ||||I=Indirect
+# |        |||W=warning
+# |        ||C=constructor
+# |        |w=weak
+# |        g=global,l=local,!=both global/local
+# Size of the symbol
+#
+# GLIBC_2.2 is the version string associated to the symbol
+# (GLIBC_2.2) is the same but the symbol is hidden, a newer version of the
+# symbol exist
+
+sub parse_dynamic_symbol {
+    my ($self, $line) = @_;
+    my $vis_re = '(\.protected|\.hidden|\.internal|0x\S+)';
+    if ($line =~ /^[0-9a-f]+ (.{7})\s+(\S+)\s+[0-9a-f]+(?:\s+(\S+))?(?:\s+$vis_re)?\s+(\S+)/) {
+
+	my ($flags, $sect, $ver, $vis, $name) = ($1, $2, $3, $4, $5);
+
+	# Special case if version is missing but extra visibility
+	# attribute replaces it in the match
+	if (defined($ver) and $ver =~ /^$vis_re$/) {
+	    $vis = $ver;
+	    $ver = '';
+	}
+
+	# Cleanup visibility field
+	$vis =~ s/^\.// if defined($vis);
+
+	my $symbol = {
+		name => $name,
+		version => defined($ver) ? $ver : '',
+		section => $sect,
+		dynamic => substr($flags, 5, 1) eq 'D',
+		debug => substr($flags, 5, 1) eq 'd',
+		type => substr($flags, 6, 1),
+		weak => substr($flags, 1, 1) eq 'w',
+		local => substr($flags, 0, 1) eq 'l',
+		global => substr($flags, 0, 1) eq 'g',
+		visibility => defined($vis) ? $vis : '',
+		hidden => '',
+		defined => $sect ne '*UND*'
+	    };
+
+	# Handle hidden symbols
+	if (defined($ver) and $ver =~ /^\((.*)\)$/) {
+	    $ver = $1;
+	    $symbol->{version} = $1;
+	    $symbol->{hidden} = 1;
+	}
+
+	# Register symbol
+	$self->add_dynamic_symbol($symbol);
+    } elsif ($line =~ /^[0-9a-f]+ (.{7})\s+(\S+)\s+[0-9a-f]+/) {
+	# Same start but no version and no symbol ... just ignore
+    } elsif ($line =~ /^REG_G\d+\s+/) {
+	# Ignore some s390-specific output like
+	# REG_G6           g     R *UND*      0000000000000000              #scratch
+    } else {
+	warning(_g("couldn't parse dynamic symbol definition: %s"), $line);
+    }
+}
+
+sub apply_relocations {
+    my ($self) = @_;
+    foreach my $sym (values %{$self->{dynsyms}}) {
+	# We want to mark as undefined symbols those which are currently
+	# defined but that depend on a copy relocation
+	next if not $sym->{defined};
+	next if not exists $self->{dynrelocs}{$sym->{name}};
+	if ($self->{dynrelocs}{$sym->{name}} =~ /^R_.*_COPY$/) {
+	    $sym->{defined} = 0;
+	}
+    }
+}
+
+sub add_dynamic_symbol {
+    my ($self, $symbol) = @_;
+    $symbol->{objid} = $symbol->{soname} = $self->get_id();
+    $symbol->{soname} =~ s{^.*/}{} unless $self->{SONAME};
+    if ($symbol->{version}) {
+	$self->{dynsyms}{$symbol->{name} . '@' . $symbol->{version}} = $symbol;
+    } else {
+	$self->{dynsyms}{$symbol->{name} . '@Base'} = $symbol;
+    }
+}
+
+sub get_id {
+    my $self = shift;
+    return $self->{SONAME} || $self->{file};
+}
+
+sub get_symbol {
+    my ($self, $name) = @_;
+    if (exists $self->{dynsyms}{$name}) {
+	return $self->{dynsyms}{$name};
+    }
+    if ($name !~ /@/) {
+        if (exists $self->{dynsyms}{$name . '@Base'}) {
+            return $self->{dynsyms}{$name . '@Base'};
+        }
+    }
+    return;
+}
+
+sub get_exported_dynamic_symbols {
+    my ($self) = @_;
+    return grep { $_->{defined} && $_->{dynamic} && !$_->{local} }
+	    values %{$self->{dynsyms}};
+}
+
+sub get_undefined_dynamic_symbols {
+    my ($self) = @_;
+    return grep { (!$_->{defined}) && $_->{dynamic} }
+	    values %{$self->{dynsyms}};
+}
+
+sub get_needed_libraries {
+    my $self = shift;
+    return @{$self->{NEEDED}};
+}
+
+sub is_executable {
+    my $self = shift;
+    return exists $self->{flags}{EXEC_P} && $self->{flags}{EXEC_P};
+}
+
+sub is_public_library {
+    my $self = shift;
+    return exists $self->{flags}{DYNAMIC} && $self->{flags}{DYNAMIC}
+	&& exists $self->{SONAME} && $self->{SONAME};
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Symbol.pm b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Symbol.pm
new file mode 100644
index 0000000..c39624e
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/Symbol.pm
@@ -0,0 +1,507 @@
+# Copyright © 2007 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2009-2010 Modestas Vainius <modax@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Shlibs::Symbol;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::Gettext;
+use Dpkg::Deps;
+use Dpkg::ErrorHandling;
+use Dpkg::Util qw(:list);
+use Dpkg::Version;
+use Storable ();
+use Dpkg::Shlibs::Cppfilt;
+
+# Supported alias types in the order of matching preference
+use constant ALIAS_TYPES => qw(c++ symver);
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my %args = @_;
+    my $self = bless {
+	symbol => undef,
+	symbol_templ => undef,
+	minver => undef,
+	dep_id => 0,
+	deprecated => 0,
+	tags => {},
+	tagorder => [],
+    }, $class;
+    $self->{$_} = $args{$_} foreach keys %args;
+    return $self;
+}
+
+# Deep clone
+sub clone {
+    my $self = shift;
+    my $clone = Storable::dclone($self);
+    if (@_) {
+	my %args=@_;
+	$clone->{$_} = $args{$_} foreach keys %args;
+    }
+    return $clone;
+}
+
+sub parse_tagspec {
+    my ($self, $tagspec) = @_;
+
+    if ($tagspec =~ /^\s*\((.*?)\)(.*)$/ && $1) {
+	# (tag1=t1 value|tag2|...|tagN=tNp)
+	# Symbols ()|= cannot appear in the tag names and values
+	my $tagspec = $1;
+	my $rest = ($2) ? $2 : '';
+	my @tags = split(/\|/, $tagspec);
+
+	# Parse each tag
+	for my $tag (@tags) {
+	    if ($tag =~ /^(.*)=(.*)$/) {
+		# Tag with value
+		$self->add_tag($1, $2);
+	    } else {
+		# Tag without value
+		$self->add_tag($tag, undef);
+	    }
+	}
+	return $rest;
+    }
+    return;
+}
+
+sub parse_symbolspec {
+    my ($self, $symbolspec, %opts) = @_;
+    my $symbol;
+    my $symbol_templ;
+    my $symbol_quoted;
+    my $rest;
+
+    if (defined($symbol = $self->parse_tagspec($symbolspec))) {
+	# (tag1=t1 value|tag2|...|tagN=tNp)"Foo::Bar::foobar()"@Base 1.0 1
+	# Symbols ()|= cannot appear in the tag names and values
+
+	# If the tag specification exists symbol name template might be quoted too
+	if ($symbol =~ /^(['"])/ && $symbol =~ /^($1)(.*?)$1(.*)$/) {
+	    $symbol_quoted = $1;
+	    $symbol_templ = $2;
+	    $symbol = $2;
+	    $rest = $3;
+	} else {
+	    if ($symbol =~ m/^(\S+)(.*)$/) {
+		$symbol_templ = $1;
+		$symbol = $1;
+		$rest = $2;
+	    }
+	}
+	error(_g('symbol name unspecified: %s'), $symbolspec) if (!$symbol);
+    } else {
+	# No tag specification. Symbol name is up to the first space
+	# foobarsymbol@Base 1.0 1
+	if ($symbolspec =~ m/^(\S+)(.*)$/) {
+	    $symbol = $1;
+	    $rest = $2;
+	} else {
+	    return 0;
+	}
+    }
+    $self->{symbol} = $symbol;
+    $self->{symbol_templ} = $symbol_templ;
+    $self->{symbol_quoted} = $symbol_quoted if ($symbol_quoted);
+
+    # Now parse "the rest" (minver and dep_id)
+    if ($rest =~ /^\s(\S+)(?:\s(\d+))?/) {
+	$self->{minver} = $1;
+	$self->{dep_id} = defined($2) ? $2 : 0;
+    } elsif (defined $opts{default_minver}) {
+	$self->{minver} = $opts{default_minver};
+	$self->{dep_id} = 0;
+    } else {
+	return 0;
+    }
+    return 1;
+}
+
+# A hook for symbol initialization (typically processing of tags). The code
+# here may even change symbol name. Called from
+# Dpkg::Shlibs::SymbolFile::create_symbol().
+sub initialize {
+    my $self = shift;
+
+    # Look for tags marking symbol patterns. The pattern may match multiple
+    # real symbols.
+    my $type;
+    if ($self->has_tag('c++')) {
+	# Raw symbol name is always demangled to the same alias while demangled
+	# symbol name cannot be reliably converted back to raw symbol name.
+	# Therefore, we can use hash for mapping.
+	$type = 'alias-c++';
+    }
+
+    # Support old style wildcard syntax. That's basically a symver
+    # with an optional tag.
+    if ($self->get_symbolname() =~ /^\*@(.*)$/) {
+	$self->add_tag('symver') unless $self->has_tag('symver');
+	$self->add_tag('optional') unless $self->has_tag('optional');
+	$self->{symbol} = $1;
+    }
+
+    if ($self->has_tag('symver')) {
+	# Each symbol is matched against its version rather than full
+	# name@version string.
+	$type = (defined $type) ? 'generic' : 'alias-symver';
+	if ($self->get_symbolname() eq 'Base') {
+	    error(_g("you can't use symver tag to catch unversioned symbols: %s"),
+	          $self->get_symbolspec(1));
+	}
+    }
+
+    # As soon as regex is involved, we need to match each real
+    # symbol against each pattern (aka 'generic' pattern).
+    if ($self->has_tag('regex')) {
+	$type = 'generic';
+	# Pre-compile regular expression for better performance.
+	my $regex = $self->get_symbolname();
+	$self->{pattern}{regex} = qr/$regex/;
+    }
+    if (defined $type) {
+	$self->init_pattern($type);
+    }
+}
+
+sub get_symbolname {
+    return $_[0]->{symbol};
+}
+
+sub get_symboltempl {
+    return $_[0]->{symbol_templ} || $_[0]->{symbol};
+}
+
+sub set_symbolname {
+    my ($self, $name, $templ, $quoted) = @_;
+    unless (defined $name) {
+	$name = $self->{symbol};
+    }
+    if (!defined $templ && $name =~ /\s/) {
+	$templ = $name;
+    }
+    if (!defined $quoted && defined $templ && $templ =~ /\s/) {
+	$quoted = '"';
+    }
+    $self->{symbol} = $name;
+    $self->{symbol_templ} = $templ;
+    if ($quoted) {
+	$self->{symbol_quoted} = $quoted;
+    } else {
+	delete $self->{symbol_quoted};
+    }
+}
+
+sub has_tags {
+    my $self = shift;
+    return scalar (@{$self->{tagorder}});
+}
+
+sub add_tag {
+    my ($self, $tagname, $tagval) = @_;
+    if (exists $self->{tags}{$tagname}) {
+	$self->{tags}{$tagname} = $tagval;
+	return 0;
+    } else {
+	$self->{tags}{$tagname} = $tagval;
+	push @{$self->{tagorder}}, $tagname;
+    }
+    return 1;
+}
+
+sub delete_tag {
+    my ($self, $tagname) = @_;
+    if (exists $self->{tags}{$tagname}) {
+	delete $self->{tags}{$tagname};
+        $self->{tagorder} = [ grep { $_ ne $tagname } @{$self->{tagorder}} ];
+	return 1;
+    }
+    return 0;
+}
+
+sub has_tag {
+    my ($self, $tag) = @_;
+    return exists $self->{tags}{$tag};
+}
+
+sub get_tag_value {
+    my ($self, $tag) = @_;
+    return $self->{tags}{$tag};
+}
+
+# Checks if the symbol is equal to another one (by name and optionally,
+# tag sets, versioning info (minver and depid))
+sub equals {
+    my ($self, $other, %opts) = @_;
+    $opts{versioning} = 1 unless exists $opts{versioning};
+    $opts{tags} = 1 unless exists $opts{tags};
+
+    return 0 if $self->{symbol} ne $other->{symbol};
+
+    if ($opts{versioning}) {
+	return 0 if $self->{minver} ne $other->{minver};
+	return 0 if $self->{dep_id} ne $other->{dep_id};
+    }
+
+    if ($opts{tags}) {
+	return 0 if scalar(@{$self->{tagorder}}) != scalar(@{$other->{tagorder}});
+
+	for my $i (0 .. scalar(@{$self->{tagorder}}) - 1) {
+	    my $tag = $self->{tagorder}->[$i];
+	    return 0 if $tag ne $other->{tagorder}->[$i];
+	    if (defined $self->{tags}{$tag} && defined $other->{tags}{$tag}) {
+		return 0 if $self->{tags}{$tag} ne $other->{tags}{$tag};
+	    } elsif (defined $self->{tags}{$tag} || defined $other->{tags}{$tag}) {
+		return 0;
+	    }
+	}
+    }
+
+    return 1;
+}
+
+
+sub is_optional {
+    my $self = shift;
+    return $self->has_tag('optional');
+}
+
+sub is_arch_specific {
+    my $self = shift;
+    return $self->has_tag('arch');
+}
+
+sub arch_is_concerned {
+    my ($self, $arch) = @_;
+    my $arches = $self->{tags}{arch};
+
+    if (defined $arch && defined $arches) {
+	my $dep = Dpkg::Deps::Simple->new();
+	my @arches = split(/[\s,]+/, $arches);
+	$dep->{package} = 'dummy';
+	$dep->{arches} = \@arches;
+	return $dep->arch_is_concerned($arch);
+    }
+
+    return 1;
+}
+
+# Get reference to the pattern the symbol matches (if any)
+sub get_pattern {
+    return $_[0]->{matching_pattern};
+}
+
+### NOTE: subroutines below require (or initialize) $self to be a pattern ###
+
+# Initializes this symbol as a pattern of the specified type.
+sub init_pattern {
+    my ($self, $type) = @_;
+
+    $self->{pattern}{type} = $type;
+    # To be filled with references to symbols matching this pattern.
+    $self->{pattern}{matches} = [];
+}
+
+# Is this symbol a pattern or not?
+sub is_pattern {
+    return exists $_[0]->{pattern};
+}
+
+# Get pattern type if this symbol is a pattern.
+sub get_pattern_type {
+    return $_[0]->{pattern}{type} || '';
+}
+
+# Get (sub)type of the alias pattern. Returns empty string if current
+# pattern is not alias.
+sub get_alias_type {
+    return ($_[0]->get_pattern_type() =~ /^alias-(.+)/ && $1) || '';
+}
+
+# Get a list of symbols matching this pattern if this symbol is a pattern
+sub get_pattern_matches {
+    return @{$_[0]->{pattern}{matches}};
+}
+
+# Create a new symbol based on the pattern (i.e. $self)
+# and add it to the pattern matches list.
+sub create_pattern_match {
+    my $self = shift;
+    return unless $self->is_pattern();
+
+    # Leave out 'pattern' subfield while deep-cloning
+    my $pattern_stuff = $self->{pattern};
+    delete $self->{pattern};
+    my $newsym = $self->clone(@_);
+    $self->{pattern} = $pattern_stuff;
+
+    # Clean up symbol name related internal fields
+    $newsym->set_symbolname();
+
+    # Set newsym pattern reference, add to pattern matches list
+    $newsym->{matching_pattern} = $self;
+    push @{$self->{pattern}{matches}}, $newsym;
+    return $newsym;
+}
+
+### END of pattern subroutines ###
+
+# Given a raw symbol name the call returns its alias according to the rules of
+# the current pattern ($self). Returns undef if the supplied raw name is not
+# transformable to alias.
+sub convert_to_alias {
+    my ($self, $rawname, $type) = @_;
+    $type = $self->get_alias_type() unless $type;
+
+    if ($type) {
+	if ($type eq 'symver') {
+	    # In case of symver, alias is symbol version. Extract it from the
+	    # rawname.
+	    return "$1" if ($rawname =~ /\@([^@]+)$/);
+	} elsif ($rawname =~ /^_Z/ && $type eq 'c++') {
+	    return cppfilt_demangle_cpp($rawname);
+	}
+    }
+    return;
+}
+
+sub get_tagspec {
+    my ($self) = @_;
+    if ($self->has_tags()) {
+	my @tags;
+	for my $tagname (@{$self->{tagorder}}) {
+	    my $tagval = $self->{tags}{$tagname};
+	    if (defined $tagval) {
+		push @tags, $tagname . '='  . $tagval;
+	    } else {
+		push @tags, $tagname;
+	    }
+	}
+	return '(' . join('|', @tags) . ')';
+    }
+    return '';
+}
+
+sub get_symbolspec {
+    my $self = shift;
+    my $template_mode = shift;
+    my $spec = '';
+    $spec .= "#MISSING: $self->{deprecated}#" if $self->{deprecated};
+    $spec .= ' ';
+    if ($template_mode) {
+	if ($self->has_tags()) {
+	    $spec .= sprintf('%s%3$s%s%3$s', $self->get_tagspec(),
+		$self->get_symboltempl(), $self->{symbol_quoted} || '');
+	} else {
+	    $spec .= $self->get_symboltempl();
+	}
+    } else {
+	$spec .= $self->get_symbolname();
+    }
+    $spec .= " $self->{minver}";
+    $spec .= " $self->{dep_id}" if $self->{dep_id};
+    return $spec;
+}
+
+# Sanitize the symbol when it is confirmed to be found in
+# the respective library.
+sub mark_found_in_library {
+    my ($self, $minver, $arch) = @_;
+
+    if ($self->{deprecated}) {
+	# Symbol reappeared somehow
+	$self->{deprecated} = 0;
+	$self->{minver} = $minver if (not $self->is_optional());
+    } else {
+	# We assume that the right dependency information is already
+	# there.
+	if (version_compare($minver, $self->{minver}) < 0) {
+	    $self->{minver} = $minver;
+	}
+    }
+    # Never remove arch tags from patterns
+    if (not $self->is_pattern()) {
+	if (not $self->arch_is_concerned($arch)) {
+	    # Remove arch tag because it is incorrect.
+	    $self->delete_tag('arch');
+	}
+    }
+}
+
+# Sanitize the symbol when it is confirmed to be NOT found in
+# the respective library.
+# Mark as deprecated those that are no more provided (only if the
+# minver is later than the version where the symbol was introduced)
+sub mark_not_found_in_library {
+    my ($self, $minver, $arch) = @_;
+
+    # Ignore symbols from foreign arch
+    return if not $self->arch_is_concerned($arch);
+
+    if ($self->{deprecated}) {
+	# Bump deprecated if the symbol is optional so that it
+	# keeps reappering in the diff while it's missing
+	$self->{deprecated} = $minver if $self->is_optional();
+    } elsif (version_compare($minver, $self->{minver}) > 0) {
+	$self->{deprecated} = $minver;
+    }
+}
+
+# Checks if the symbol (or pattern) is legitimate as a real symbol for the
+# specified architecture.
+sub is_legitimate {
+    my ($self, $arch) = @_;
+    return ! $self->{deprecated} &&
+           $self->arch_is_concerned($arch);
+}
+
+# Determine whether a supplied raw symbol name matches against current ($self)
+# symbol or pattern.
+sub matches_rawname {
+    my ($self, $rawname) = @_;
+    my $target = $rawname;
+    my $ok = 1;
+    my $do_eq_match = 1;
+
+    if ($self->is_pattern()) {
+	# Process pattern tags in the order they were specified.
+	for my $tag (@{$self->{tagorder}}) {
+	    if (any { $tag eq $_ } ALIAS_TYPES) {
+		$ok = not not ($target = $self->convert_to_alias($target, $tag));
+	    } elsif ($tag eq 'regex') {
+		# Symbol name is a regex. Match it against the target
+		$do_eq_match = 0;
+		$ok = ($target =~ $self->{pattern}{regex});
+	    }
+	    last if not $ok;
+	}
+    }
+
+    # Equality match by default
+    if ($ok && $do_eq_match) {
+	$ok = $target eq $self->get_symbolname();
+    }
+    return $ok;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Shlibs/SymbolFile.pm b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/SymbolFile.pm
new file mode 100644
index 0000000..1287d1e
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Shlibs/SymbolFile.pm
@@ -0,0 +1,652 @@
+# Copyright © 2007 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2009-2010 Modestas Vainius <modax@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Shlibs::SymbolFile;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Version;
+use Dpkg::Control::Fields;
+use Dpkg::Shlibs::Symbol;
+use Dpkg::Arch qw(get_host_arch);
+
+use parent qw(Dpkg::Interface::Storable);
+
+my %blacklist = (
+    __bss_end__ => 1,                   # arm
+    __bss_end => 1,                     # arm
+    _bss_end__ => 1,                    # arm
+    __bss_start => 1,                   # ALL
+    __bss_start__ => 1,                 # arm
+    __data_start => 1,                  # arm
+    __do_global_ctors_aux => 1,         # ia64
+    __do_global_dtors_aux => 1,         # ia64
+    __do_jv_register_classes => 1,      # ia64
+    _DYNAMIC => 1,                      # ALL
+    _edata => 1,                        # ALL
+    _end => 1,                          # ALL
+    __end__ => 1,                       # arm
+    __exidx_end => 1,                   # armel
+    __exidx_start => 1,                 # armel
+    _fbss => 1,                         # mips, mipsel
+    _fdata => 1,                        # mips, mipsel
+    _fini => 1,                         # ALL
+    _ftext => 1,                        # mips, mipsel
+    _GLOBAL_OFFSET_TABLE_ => 1,         # hppa, mips, mipsel
+    __gmon_start__ => 1,                # hppa
+    __gnu_local_gp => 1,                # mips, mipsel
+    _gp => 1,                           # mips, mipsel
+    _init => 1,                         # ALL
+    _PROCEDURE_LINKAGE_TABLE_ => 1,     # sparc, alpha
+    _SDA2_BASE_ => 1,                   # powerpc
+    _SDA_BASE_ => 1,                    # powerpc
+);
+
+for my $i (14 .. 31) {
+    # Many powerpc specific symbols
+    $blacklist{"_restfpr_$i"} = 1;
+    $blacklist{"_restfpr_$i\_x"} = 1;
+    $blacklist{"_restgpr_$i"} = 1;
+    $blacklist{"_restgpr_$i\_x"} = 1;
+    $blacklist{"_savefpr_$i"} = 1;
+    $blacklist{"_savegpr_$i"} = 1;
+}
+
+# Many armel-specific symbols
+$blacklist{"__aeabi_$_"} = 1 foreach (qw(cdcmpeq cdcmple cdrcmple cfcmpeq
+cfcmple cfrcmple d2f d2iz d2lz d2uiz d2ulz dadd dcmpeq dcmpge dcmpgt
+dcmple dcmplt dcmpun ddiv dmul dneg drsub dsub f2d f2iz f2lz f2uiz f2ulz
+fadd fcmpeq fcmpge fcmpgt fcmple fcmplt fcmpun fdiv fmul fneg frsub fsub
+i2d i2f idiv idivmod l2d l2f lasr lcmp ldivmod llsl llsr lmul ui2d ui2f
+uidiv uidivmod ul2d ul2f ulcmp uldivmod unwind_cpp_pr0 unwind_cpp_pr1
+unwind_cpp_pr2 uread4 uread8 uwrite4 uwrite8));
+
+sub new {
+    my $this = shift;
+    my %opts=@_;
+    my $class = ref($this) || $this;
+    my $self = \%opts;
+    bless $self, $class;
+    $self->{arch} //= get_host_arch();
+    $self->clear();
+    if (exists $self->{file}) {
+	$self->load($self->{file}) if -e $self->{file};
+    }
+    return $self;
+}
+
+sub get_arch {
+    my ($self) = @_;
+    return $self->{arch};
+}
+
+sub clear {
+    my ($self) = @_;
+    $self->{objects} = {};
+}
+
+sub clear_except {
+    my ($self, @ids) = @_;
+    my %has;
+    $has{$_} = 1 foreach (@ids);
+    foreach my $objid (keys %{$self->{objects}}) {
+	delete $self->{objects}{$objid} unless exists $has{$objid};
+    }
+}
+
+sub get_sonames {
+    my ($self) = @_;
+    return keys %{$self->{objects}};
+}
+
+sub get_symbols {
+    my ($self, $soname) = @_;
+    if (defined $soname) {
+	my $obj = $self->get_object($soname);
+	return (defined $obj) ? values %{$obj->{syms}} : ();
+    } else {
+	my @syms;
+	foreach my $soname ($self->get_sonames()) {
+	    push @syms, $self->get_symbols($soname);
+	}
+	return @syms;
+    }
+}
+
+sub get_patterns {
+    my ($self, $soname) = @_;
+    my @patterns;
+    if (defined $soname) {
+	my $obj = $self->get_object($soname);
+	foreach my $alias (values %{$obj->{patterns}{aliases}}) {
+	    push @patterns, values %$alias;
+	}
+	return (@patterns, @{$obj->{patterns}{generic}});
+    } else {
+	foreach my $soname ($self->get_sonames()) {
+	    push @patterns, $self->get_patterns($soname);
+	}
+	return @patterns;
+    }
+}
+
+# Create a symbol from the supplied string specification.
+sub create_symbol {
+    my ($self, $spec, %opts) = @_;
+    my $symbol = (exists $opts{base}) ? $opts{base} :
+	Dpkg::Shlibs::Symbol->new();
+
+    my $ret = $opts{dummy} ? $symbol->parse_symbolspec($spec, default_minver => 0) :
+	$symbol->parse_symbolspec($spec);
+    if ($ret) {
+	$symbol->initialize(arch => $self->get_arch());
+	return $symbol;
+    }
+    return;
+}
+
+sub add_symbol {
+    my ($self, $symbol, $soname) = @_;
+    my $object = $self->get_object($soname);
+
+    if ($symbol->is_pattern()) {
+	if (my $alias_type = $symbol->get_alias_type()) {
+	    unless (exists $object->{patterns}{aliases}{$alias_type}) {
+		$object->{patterns}{aliases}{$alias_type} = {};
+	    }
+	    # Alias hash for matching.
+	    my $aliases = $object->{patterns}{aliases}{$alias_type};
+	    $aliases->{$symbol->get_symbolname()} = $symbol;
+	} else {
+	    # Otherwise assume this is a generic sequential pattern. This
+	    # should be always safe.
+	    push @{$object->{patterns}{generic}}, $symbol;
+	}
+	return 'pattern';
+    } else {
+	# invalidate the minimum version cache
+        $object->{minver_cache} = [];
+	$object->{syms}{$symbol->get_symbolname()} = $symbol;
+	return 'sym';
+    }
+}
+
+sub _new_symbol {
+    my $base = shift || 'Dpkg::Shlibs::Symbol';
+    return (ref $base) ? $base->clone(@_) : $base->new(@_);
+}
+
+# Parameter seen is only used for recursive calls
+sub parse {
+    my ($self, $fh, $file, $seen, $obj_ref, $base_symbol) = @_;
+
+    if (defined($seen)) {
+	return if exists $seen->{$file}; # Avoid include loops
+    } else {
+	$self->{file} = $file;
+	$seen = {};
+    }
+    $seen->{$file} = 1;
+
+    if (not ref($obj_ref)) { # Init ref to name of current object/lib
+        $$obj_ref = undef;
+    }
+
+    while (defined($_ = <$fh>)) {
+	chomp($_);
+
+	if (/^(?:\s+|#(?:DEPRECATED|MISSING): ([^#]+)#\s*)(.*)/) {
+	    if (not defined ($$obj_ref)) {
+		error(_g('symbol information must be preceded by a header (file %s, line %s)'), $file, $.);
+	    }
+	    # Symbol specification
+	    my $deprecated = ($1) ? $1 : 0;
+	    my $sym = _new_symbol($base_symbol, deprecated => $deprecated);
+	    if ($self->create_symbol($2, base => $sym)) {
+		$self->add_symbol($sym, $$obj_ref);
+	    } else {
+		warning(_g('failed to parse line in %s: %s'), $file, $_);
+	    }
+	} elsif (/^(\(.*\))?#include\s+"([^"]+)"/) {
+	    my $tagspec = $1;
+	    my $filename = $2;
+	    my $dir = $file;
+	    my $new_base_symbol;
+	    if (defined $tagspec) {
+                $new_base_symbol = _new_symbol($base_symbol);
+		$new_base_symbol->parse_tagspec($tagspec);
+	    }
+	    $dir =~ s{[^/]+$}{}; # Strip filename
+	    $self->load("$dir$filename", $seen, $obj_ref, $new_base_symbol);
+	} elsif (/^#|^$/) {
+	    # Skip possible comments and empty lines
+	} elsif (/^\|\s*(.*)$/) {
+	    # Alternative dependency template
+	    push @{$self->{objects}{$$obj_ref}{deps}}, "$1";
+	} elsif (/^\*\s*([^:]+):\s*(.*\S)\s*$/) {
+	    # Add meta-fields
+	    $self->{objects}{$$obj_ref}{fields}{field_capitalize($1)} = $2;
+	} elsif (/^(\S+)\s+(.*)$/) {
+	    # New object and dependency template
+	    $$obj_ref = $1;
+	    if (exists $self->{objects}{$$obj_ref}) {
+		# Update/override infos only
+		$self->{objects}{$$obj_ref}{deps} = [ "$2" ];
+	    } else {
+		# Create a new object
+		$self->create_object($$obj_ref, "$2");
+	    }
+	} else {
+	    warning(_g('failed to parse a line in %s: %s'), $file, $_);
+	}
+    }
+    delete $seen->{$file};
+}
+
+# Beware: we reuse the data structure of the provided symfile so make
+# sure to not modify them after having called this function
+sub merge_object_from_symfile {
+    my ($self, $src, $objid) = @_;
+    if (not $self->has_object($objid)) {
+        $self->{objects}{$objid} = $src->get_object($objid);
+    } else {
+        warning(_g('tried to merge the same object (%s) twice in a symfile'), $objid);
+    }
+}
+
+sub output {
+    my ($self, $fh, %opts) = @_;
+    $opts{template_mode} = 0 unless exists $opts{template_mode};
+    $opts{with_deprecated} = 1 unless exists $opts{with_deprecated};
+    $opts{with_pattern_matches} = 0 unless exists $opts{with_pattern_matches};
+    my $res = '';
+    foreach my $soname (sort $self->get_sonames()) {
+	my @deps = $self->get_dependencies($soname);
+	my $dep_first = shift @deps;
+	$dep_first =~ s/#PACKAGE#/$opts{package}/g if exists $opts{package};
+	print { $fh } "$soname $dep_first\n" if defined $fh;
+	$res .= "$soname $dep_first\n" if defined wantarray;
+
+	foreach my $dep_next (@deps) {
+	    $dep_next =~ s/#PACKAGE#/$opts{package}/g if exists $opts{package};
+	    print { $fh } "| $dep_next\n" if defined $fh;
+	    $res .= "| $dep_next\n" if defined wantarray;
+	}
+	my $f = $self->{objects}{$soname}{fields};
+	foreach my $field (sort keys %{$f}) {
+	    my $value = $f->{$field};
+	    $value =~ s/#PACKAGE#/$opts{package}/g if exists $opts{package};
+	    print { $fh } "* $field: $value\n" if defined $fh;
+	    $res .= "* $field: $value\n" if defined wantarray;
+	}
+
+	my @symbols;
+	if ($opts{template_mode}) {
+	    # Exclude symbols matching a pattern, but include patterns themselves
+	    @symbols = grep { not $_->get_pattern() } $self->get_symbols($soname);
+	    push @symbols, $self->get_patterns($soname);
+	} else {
+	    @symbols = $self->get_symbols($soname);
+	}
+	foreach my $sym (sort { $a->get_symboltempl() cmp
+	                        $b->get_symboltempl() } @symbols) {
+	    next if $sym->{deprecated} and not $opts{with_deprecated};
+	    # Do not dump symbols from foreign arch unless dumping a template.
+	    next if not $opts{template_mode} and
+	            not $sym->arch_is_concerned($self->get_arch());
+	    # Dump symbol specification. Dump symbol tags only in template mode.
+	    print { $fh } $sym->get_symbolspec($opts{template_mode}), "\n" if defined $fh;
+	    $res .= $sym->get_symbolspec($opts{template_mode}) . "\n" if defined wantarray;
+	    # Dump pattern matches as comments (if requested)
+	    if ($opts{with_pattern_matches} && $sym->is_pattern()) {
+		for my $match (sort { $a->get_symboltempl() cmp
+		                      $b->get_symboltempl() } $sym->get_pattern_matches())
+		{
+		    print { $fh } '#MATCH:', $match->get_symbolspec(0), "\n" if defined $fh;
+		    $res .= '#MATCH:' . $match->get_symbolspec(0) . "\n" if defined wantarray;
+		}
+	    }
+	}
+    }
+    return $res;
+}
+
+# Tries to match a symbol name and/or version against the patterns defined.
+# Returns a pattern which matches (if any).
+sub find_matching_pattern {
+    my ($self, $refsym, $sonames, $inc_deprecated) = @_;
+    $inc_deprecated //= 0;
+    my $name = (ref $refsym) ? $refsym->get_symbolname() : $refsym;
+
+    my $pattern_ok = sub {
+	my $p = shift;
+	return defined $p && ($inc_deprecated || !$p->{deprecated}) &&
+	       $p->arch_is_concerned($self->get_arch());
+    };
+
+    foreach my $soname ((ref($sonames) eq 'ARRAY') ? @$sonames : $sonames) {
+	my $obj = $self->get_object($soname);
+	my ($type, $pattern);
+	next unless defined $obj;
+
+	my $all_aliases = $obj->{patterns}{aliases};
+	for my $type (Dpkg::Shlibs::Symbol::ALIAS_TYPES) {
+	    if (exists $all_aliases->{$type} && keys(%{$all_aliases->{$type}})) {
+		my $aliases = $all_aliases->{$type};
+		my $converter = $aliases->{(keys %$aliases)[0]};
+		if (my $alias = $converter->convert_to_alias($name)) {
+		    if ($alias && exists $aliases->{$alias}) {
+			$pattern = $aliases->{$alias};
+			last if &$pattern_ok($pattern);
+			$pattern = undef; # otherwise not found yet
+		    }
+		}
+	    }
+	}
+
+	# Now try generic patterns and use the first that matches
+	if (not defined $pattern) {
+	    for my $p (@{$obj->{patterns}{generic}}) {
+		if (&$pattern_ok($p) && $p->matches_rawname($name)) {
+		    $pattern = $p;
+		    last;
+		}
+	    }
+	}
+	if (defined $pattern) {
+	    return (wantarray) ?
+		( symbol => $pattern, soname => $soname ) : $pattern;
+	}
+    }
+    return;
+}
+
+# merge_symbols($object, $minver)
+# Needs $Objdump->get_object($soname) as parameter
+# Don't merge blacklisted symbols related to the internal (arch-specific)
+# machinery
+sub merge_symbols {
+    my ($self, $object, $minver) = @_;
+
+    my $soname = $object->{SONAME};
+    error(_g('cannot merge symbols from objects without SONAME'))
+        unless $soname;
+
+    my %dynsyms;
+    foreach my $sym ($object->get_exported_dynamic_symbols()) {
+        my $name = $sym->{name} . '@' .
+                   ($sym->{version} ? $sym->{version} : 'Base');
+        my $symobj = $self->lookup_symbol($name, $soname);
+        if (exists $blacklist{$sym->{name}}) {
+            next unless (defined $symobj and $symobj->has_tag('ignore-blacklist'));
+        }
+        $dynsyms{$name} = $sym;
+    }
+
+    unless ($self->has_object($soname)) {
+	$self->create_object($soname, '');
+    }
+    # Scan all symbols provided by the objects
+    my $obj = $self->get_object($soname);
+    # invalidate the minimum version cache - it is not sufficient to
+    # invalidate in add_symbol, since we might change a minimum
+    # version for a particular symbol without adding it
+    $obj->{minver_cache} = [];
+    foreach my $name (keys %dynsyms) {
+        my $sym;
+	if ($sym = $self->lookup_symbol($name, $obj, 1)) {
+	    # If the symbol is already listed in the file
+	    $sym->mark_found_in_library($minver, $self->get_arch());
+	} else {
+	    # The exact symbol is not present in the file, but it might match a
+	    # pattern.
+	    my $pattern = $self->find_matching_pattern($name, $obj, 1);
+	    if (defined $pattern) {
+		$pattern->mark_found_in_library($minver, $self->get_arch());
+		$sym = $pattern->create_pattern_match(symbol => $name);
+	    } else {
+		# Symbol without any special info as no pattern matched
+		$sym = Dpkg::Shlibs::Symbol->new(symbol => $name,
+		                                 minver => $minver);
+	    }
+	    $self->add_symbol($sym, $obj);
+	}
+    }
+
+    # Process all symbols which could not be found in the library.
+    foreach my $sym ($self->get_symbols($soname)) {
+	if (not exists $dynsyms{$sym->get_symbolname()}) {
+	    $sym->mark_not_found_in_library($minver, $self->get_arch());
+	}
+    }
+
+    # Deprecate patterns which didn't match anything
+    for my $pattern (grep { $_->get_pattern_matches() == 0 }
+                          $self->get_patterns($soname)) {
+	$pattern->mark_not_found_in_library($minver, $self->get_arch());
+    }
+}
+
+sub is_empty {
+    my ($self) = @_;
+    return scalar(keys %{$self->{objects}}) ? 0 : 1;
+}
+
+sub has_object {
+    my ($self, $soname) = @_;
+    return exists $self->{objects}{$soname};
+}
+
+sub get_object {
+    my ($self, $soname) = @_;
+    return ref($soname) ? $soname : $self->{objects}{$soname};
+}
+
+sub create_object {
+    my ($self, $soname, @deps) = @_;
+    $self->{objects}{$soname} = {
+	syms => {},
+	fields => {},
+	patterns => {
+	    aliases => {},
+	    generic => [],
+	},
+	deps => [ @deps ],
+        minver_cache => []
+    };
+}
+
+sub get_dependency {
+    my ($self, $soname, $dep_id) = @_;
+    $dep_id //= 0;
+    return $self->get_object($soname)->{deps}[$dep_id];
+}
+
+sub get_smallest_version {
+    my ($self, $soname, $dep_id) = @_;
+    $dep_id //= 0;
+    my $so_object = $self->get_object($soname);
+    return $so_object->{minver_cache}[$dep_id] if(defined($so_object->{minver_cache}[$dep_id]));
+    my $minver;
+    foreach my $sym ($self->get_symbols($so_object)) {
+        next if $dep_id != $sym->{dep_id};
+        $minver //= $sym->{minver};
+        if (version_compare($minver, $sym->{minver}) > 0) {
+            $minver = $sym->{minver};
+        }
+    }
+    $so_object->{minver_cache}[$dep_id] = $minver;
+    return $minver;
+}
+
+sub get_dependencies {
+    my ($self, $soname) = @_;
+    return @{$self->get_object($soname)->{deps}};
+}
+
+sub get_field {
+    my ($self, $soname, $name) = @_;
+    if (my $obj = $self->get_object($soname)) {
+	if (exists $obj->{fields}{$name}) {
+	    return $obj->{fields}{$name};
+	}
+    }
+    return;
+}
+
+# Tries to find a symbol like the $refsym and returns its descriptor.
+# $refsym may also be a symbol name.
+sub lookup_symbol {
+    my ($self, $refsym, $sonames, $inc_deprecated) = @_;
+    $inc_deprecated //= 0;
+    my $name = (ref $refsym) ? $refsym->get_symbolname() : $refsym;
+
+    foreach my $so ((ref($sonames) eq 'ARRAY') ? @$sonames : $sonames) {
+	if (my $obj = $self->get_object($so)) {
+	    my $sym = $obj->{syms}{$name};
+	    if ($sym and ($inc_deprecated or not $sym->{deprecated}))
+	    {
+		return (wantarray) ?
+		    ( symbol => $sym, soname => $so ) : $sym;
+	    }
+	}
+    }
+    return;
+}
+
+# Tries to find a pattern like the $refpat and returns its descriptor.
+# $refpat may also be a pattern spec.
+sub lookup_pattern {
+    my ($self, $refpat, $sonames, $inc_deprecated) = @_;
+    $inc_deprecated //= 0;
+    # If $refsym is a string, we need to create a dummy ref symbol.
+    $refpat = $self->create_symbol($refpat, dummy => 1) if ! ref($refpat);
+
+    if ($refpat && $refpat->is_pattern()) {
+	foreach my $soname ((ref($sonames) eq 'ARRAY') ? @$sonames : $sonames) {
+	    if (my $obj = $self->get_object($soname)) {
+		my $pat;
+		if (my $type = $refpat->get_alias_type()) {
+		    if (exists $obj->{patterns}{aliases}{$type}) {
+			$pat = $obj->{patterns}{aliases}{$type}{$refpat->get_symbolname()};
+		    }
+		} elsif ($refpat->get_pattern_type() eq 'generic') {
+		    for my $p (@{$obj->{patterns}{generic}}) {
+			if (($inc_deprecated || !$p->{deprecated}) &&
+			    $p->equals($refpat, versioning => 0))
+			{
+			    $pat = $p;
+			    last;
+			}
+		    }
+		}
+		if ($pat && ($inc_deprecated || !$pat->{deprecated})) {
+		    return (wantarray) ?
+			(symbol => $pat, soname => $soname) : $pat;
+		}
+	    }
+	}
+    }
+    return;
+}
+
+# Get symbol object reference either by symbol name or by a reference object.
+sub get_symbol_object {
+    my ($self, $refsym, $soname) = @_;
+    my $sym = $self->lookup_symbol($refsym, $soname, 1);
+    if (! defined $sym) {
+	$sym = $self->lookup_pattern($refsym, $soname, 1);
+    }
+    return $sym;
+}
+
+sub get_new_symbols {
+    my ($self, $ref, %opts) = @_;
+    my $with_optional = (exists $opts{with_optional}) ?
+	$opts{with_optional} : 0;
+    my @res;
+    foreach my $soname ($self->get_sonames()) {
+	next if not $ref->has_object($soname);
+
+	# Scan raw symbols first.
+	foreach my $sym (grep { ($with_optional || ! $_->is_optional())
+	                        && $_->is_legitimate($self->get_arch()) }
+	                      $self->get_symbols($soname))
+	{
+	    my $refsym = $ref->lookup_symbol($sym, $soname, 1);
+	    my $isnew;
+	    if (defined $refsym) {
+		# If the symbol exists in the $ref symbol file, it might
+		# still be new if $refsym is not legitimate.
+		$isnew = not $refsym->is_legitimate($self->get_arch());
+	    } else {
+		# If the symbol does not exist in the $ref symbol file, it does
+		# not mean that it's new. It might still match a pattern in the
+		# symbol file. However, due to performance reasons, first check
+		# if the pattern that the symbol matches (if any) exists in the
+		# ref symbol file as well.
+		$isnew = not (
+		    ($sym->get_pattern() and $ref->lookup_pattern($sym->get_pattern(), $soname, 1)) or
+		    $ref->find_matching_pattern($sym, $soname, 1)
+		);
+	    }
+	    push @res, { symbol => $sym, soname => $soname } if $isnew;
+	}
+
+	# Now scan patterns
+	foreach my $p (grep { ($with_optional || ! $_->is_optional())
+	                      && $_->is_legitimate($self->get_arch()) }
+	                    $self->get_patterns($soname))
+	{
+	    my $refpat = $ref->lookup_pattern($p, $soname, 0);
+	    # If reference pattern was not found or it is not legitimate,
+	    # considering current one as new.
+	    if (not defined $refpat or
+	        not $refpat->is_legitimate($self->get_arch()))
+	    {
+		push @res, { symbol => $p , soname => $soname };
+	    }
+	}
+    }
+    return @res;
+}
+
+sub get_lost_symbols {
+    my ($self, $ref, %opts) = @_;
+    return $ref->get_new_symbols($self, %opts);
+}
+
+
+sub get_new_libs {
+    my ($self, $ref) = @_;
+    my @res;
+    foreach my $soname ($self->get_sonames()) {
+	push @res, $soname if not $ref->get_object($soname);
+    }
+    return @res;
+}
+
+sub get_lost_libs {
+    my ($self, $ref) = @_;
+    return $ref->get_new_libs($self);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Archive.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Archive.pm
new file mode 100644
index 0000000..6257702b
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Archive.pm
@@ -0,0 +1,163 @@
+# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Archive;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::Source::Functions qw(erasedir fixperms);
+use Dpkg::Gettext;
+use Dpkg::IPC;
+use Dpkg::ErrorHandling;
+
+use Carp;
+use File::Temp qw(tempdir);
+use File::Basename qw(basename);
+use File::Spec;
+use Cwd;
+
+use parent qw(Dpkg::Compression::FileHandle);
+
+sub create {
+    my ($self, %opts) = @_;
+    $opts{options} ||= [];
+    my %spawn_opts;
+    # Possibly run tar from another directory
+    if ($opts{chdir}) {
+        $spawn_opts{chdir} = $opts{chdir};
+        *$self->{chdir} = $opts{chdir};
+    }
+    # Redirect input/output appropriately
+    $self->ensure_open('w');
+    $spawn_opts{to_handle} = $self->get_filehandle();
+    $spawn_opts{from_pipe} = \*$self->{tar_input};
+    # Call tar creation process
+    $spawn_opts{delete_env} = [ 'TAR_OPTIONS' ];
+    $spawn_opts{exec} = [ 'tar', '--null', '-T', '-', '--numeric-owner',
+                            '--owner', '0', '--group', '0',
+                            @{$opts{options}}, '-cf', '-' ];
+    *$self->{pid} = spawn(%spawn_opts);
+    *$self->{cwd} = getcwd();
+}
+
+sub _add_entry {
+    my ($self, $file) = @_;
+    my $cwd = *$self->{cwd};
+    croak 'call create() first' unless *$self->{tar_input};
+    $file = $2 if ($file =~ /^\Q$cwd\E\/(.+)$/); # Relative names
+    print({ *$self->{tar_input} } "$file\0")
+        or syserr(_g('write on tar input'));
+}
+
+sub add_file {
+    my ($self, $file) = @_;
+    my $testfile = $file;
+    if (*$self->{chdir}) {
+        $testfile = File::Spec->catfile(*$self->{chdir}, $file);
+    }
+    croak 'add_file() does not handle directories'
+        if not -l $testfile and -d _;
+    $self->_add_entry($file);
+}
+
+sub add_directory {
+    my ($self, $file) = @_;
+    my $testfile = $file;
+    if (*$self->{chdir}) {
+        $testfile = File::Spec->catdir(*$self->{chdir}, $file);
+    }
+    croak 'add_directory() only handles directories'
+        if -l $testfile or not -d _;
+    $self->_add_entry($file);
+}
+
+sub finish {
+    my ($self) = @_;
+    close(*$self->{tar_input}) or syserr(_g('close on tar input'));
+    wait_child(*$self->{pid}, cmdline => 'tar -cf -');
+    delete *$self->{pid};
+    delete *$self->{tar_input};
+    delete *$self->{cwd};
+    delete *$self->{chdir};
+    $self->close();
+}
+
+sub extract {
+    my ($self, $dest, %opts) = @_;
+    $opts{options} ||= [];
+    $opts{in_place} ||= 0;
+    $opts{no_fixperms} ||= 0;
+    my %spawn_opts = (wait_child => 1);
+
+    # Prepare destination
+    my $tmp;
+    if ($opts{in_place}) {
+        $spawn_opts{chdir} = $dest;
+        $tmp = $dest; # So that fixperms call works
+    } else {
+        my $template = basename($self->get_filename()) .  '.tmp-extract.XXXXX';
+        unless (-e $dest) {
+            # Kludge so that realpath works
+            mkdir($dest) or syserr(_g('cannot create directory %s'), $dest);
+        }
+        $tmp = tempdir($template, DIR => Cwd::realpath("$dest/.."), CLEANUP => 1);
+        $spawn_opts{chdir} = $tmp;
+    }
+
+    # Prepare stuff that handles the input of tar
+    $self->ensure_open('r');
+    $spawn_opts{from_handle} = $self->get_filehandle();
+
+    # Call tar extraction process
+    $spawn_opts{delete_env} = [ 'TAR_OPTIONS' ];
+    $spawn_opts{exec} = [ 'tar', '--no-same-owner', '--no-same-permissions',
+                            @{$opts{options}}, '-xf', '-' ];
+    spawn(%spawn_opts);
+    $self->close();
+
+    # Fix permissions on extracted files because tar insists on applying
+    # our umask _to the original permissions_ rather than mostly-ignoring
+    # the original permissions.
+    # We still need --no-same-permissions because otherwise tar might
+    # extract directory setgid (which we want inherited, not
+    # extracted); we need --no-same-owner because putting the owner
+    # back is tedious - in particular, correct group ownership would
+    # have to be calculated using mount options and other madness.
+    fixperms($tmp) unless $opts{no_fixperms};
+
+    # Stop here if we extracted in-place as there's nothing to move around
+    return if $opts{in_place};
+
+    # Rename extracted directory
+    opendir(my $dir_dh, $tmp) or syserr(_g('cannot opendir %s'), $tmp);
+    my @entries = grep { $_ ne '.' && $_ ne '..' } readdir($dir_dh);
+    closedir($dir_dh);
+    my $done = 0;
+    erasedir($dest);
+    if (scalar(@entries) == 1 && ! -l "$tmp/$entries[0]" && -d _) {
+	rename("$tmp/$entries[0]", $dest)
+	    or syserr(_g('unable to rename %s to %s'),
+	              "$tmp/$entries[0]", $dest);
+    } else {
+	rename($tmp, $dest)
+	    or syserr(_g('unable to rename %s to %s'), $tmp, $dest);
+    }
+    erasedir($tmp);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Functions.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Functions.pm
new file mode 100644
index 0000000..b61d0af0
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Functions.pm
@@ -0,0 +1,120 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Functions;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(erasedir fixperms fs_time is_binary);
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::IPC;
+
+use POSIX qw(:errno_h);
+
+sub erasedir {
+    my ($dir) = @_;
+    if (not lstat($dir)) {
+        return if $! == ENOENT;
+        syserr(_g('cannot stat directory %s (before removal)'), $dir);
+    }
+    system 'rm','-rf','--',$dir;
+    subprocerr("rm -rf $dir") if $?;
+    if (not stat($dir)) {
+        return if $! == ENOENT;
+        syserr(_g("unable to check for removal of dir `%s'"), $dir);
+    }
+    error(_g("rm -rf failed to remove `%s'"), $dir);
+}
+
+sub fixperms {
+    my ($dir) = @_;
+    my ($mode, $modes_set);
+    # Unfortunately tar insists on applying our umask _to the original
+    # permissions_ rather than mostly-ignoring the original
+    # permissions.  We fix it up with chmod -R (which saves us some
+    # work) but we have to construct a u+/- string which is a bit
+    # of a palaver.  (Numeric doesn't work because we need [ugo]+X
+    # and [ugo]=<stuff> doesn't work because that unsets sgid on dirs.)
+    $mode = 0777 & ~umask;
+    for my $i (0 .. 2) {
+        $modes_set .= ',' if $i;
+        $modes_set .= qw(u g o)[$i];
+        for my $j (0 .. 2) {
+            $modes_set .= $mode & (0400 >> ($i * 3 + $j)) ? '+' : '-';
+            $modes_set .= qw(r w X)[$j];
+        }
+    }
+    system('chmod', '-R', '--', $modes_set, $dir);
+    subprocerr("chmod -R -- $modes_set $dir") if $?;
+}
+
+# Touch the file and read the resulting mtime.
+#
+# If the file doesn't exist, create it, read the mtime and unlink it.
+#
+# Use this instead of time() when the timestamp is going to be
+# used to set file timestamps. This avoids confusion when an
+# NFS server and NFS client disagree about what time it is.
+sub fs_time($) {
+    my ($file) = @_;
+    my $is_temp = 0;
+    if (not -e $file) {
+	open(my $temp_fh, '>', $file) or syserr(_g('cannot write %s'));
+	close($temp_fh);
+	$is_temp = 1;
+    } else {
+	utime(undef, undef, $file) or
+	    syserr(_g('cannot change timestamp for %s'), $file);
+    }
+    stat($file) or syserr(_g('cannot read timestamp from %s'), $file);
+    my $mtime = (stat(_))[9];
+    unlink($file) if $is_temp;
+    return $mtime;
+}
+
+sub is_binary($) {
+    my ($file) = @_;
+
+    # TODO: might want to reimplement what diff does, aka checking if the
+    # file contains \0 in the first 4Kb of data
+
+    # Use diff to check if it's a binary file
+    my $diffgen;
+    my $diff_pid = spawn(
+        exec => [ 'diff', '-u', '--', '/dev/null', $file ],
+        env => { LC_ALL => 'C', LANG => 'C', TZ => 'UTC0' },
+        to_pipe => \$diffgen,
+    );
+    my $result = 0;
+    local $_;
+    while (<$diffgen>) {
+        if (m/^(?:binary|[^-+\@ ].*\bdiffer\b)/i) {
+            $result = 1;
+            last;
+        } elsif (m/^[-+\@ ]/) {
+            $result = 0;
+            last;
+        }
+    }
+    close($diffgen) or syserr('close on diff pipe');
+    wait_child($diff_pid, nocheck => 1, cmdline => "diff -u -- /dev/null $file");
+    return $result;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package.pm
new file mode 100644
index 0000000..ce07a79c
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package.pm
@@ -0,0 +1,631 @@
+# Copyright © 2008-2011 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Source::Package - manipulate Debian source packages
+
+=head1 DESCRIPTION
+
+This module provides an object that can manipulate Debian source
+packages. While it supports both the extraction and the creation
+of source packages, the only API that is officially supported
+is the one that supports the extraction of the source package.
+
+=head1 FUNCTIONS
+
+=cut
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control;
+use Dpkg::Checksums;
+use Dpkg::Version;
+use Dpkg::Compression;
+use Dpkg::Exit qw(run_exit_handlers);
+use Dpkg::Path qw(check_files_are_the_same find_command);
+use Dpkg::IPC;
+use Dpkg::Vendor qw(run_vendor_hook);
+
+use Carp;
+use POSIX qw(:errno_h :sys_wait_h);
+use File::Basename;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(get_default_diff_ignore_regex
+                    set_default_diff_ignore_regex
+                    get_default_tar_ignore_pattern);
+
+my $diff_ignore_default_regex = '
+# Ignore general backup files
+(?:^|/).*~$|
+# Ignore emacs recovery files
+(?:^|/)\.#.*$|
+# Ignore vi swap files
+(?:^|/)\..*\.sw.$|
+# Ignore baz-style junk files or directories
+(?:^|/),,.*(?:$|/.*$)|
+# File-names that should be ignored (never directories)
+(?:^|/)(?:DEADJOE|\.arch-inventory|\.(?:bzr|cvs|hg|git)ignore)$|
+# File or directory names that should be ignored
+(?:^|/)(?:CVS|RCS|\.deps|\{arch\}|\.arch-ids|\.svn|
+\.hg(?:tags|sigs)?|_darcs|\.git(?:attributes|modules)?|
+\.shelf|_MTN|\.be|\.bzr(?:\.backup|tags)?)(?:$|/.*$)
+';
+# Take out comments and newlines
+$diff_ignore_default_regex =~ s/^#.*$//mg;
+$diff_ignore_default_regex =~ s/\n//sg;
+
+# Public variables
+# XXX: Backwards compatibility, stop exporting on VERSION 2.00.
+## no critic (Variables::ProhibitPackageVars)
+our $diff_ignore_default_regexp;
+*diff_ignore_default_regexp = \$diff_ignore_default_regex;
+
+no warnings 'qw'; ## no critic (TestingAndDebugging::ProhibitNoWarnings)
+our @tar_ignore_default_pattern = qw(
+*.a
+*.la
+*.o
+*.so
+.*.sw?
+*/*~
+,,*
+.[#~]*
+.arch-ids
+.arch-inventory
+.be
+.bzr
+.bzr.backup
+.bzr.tags
+.bzrignore
+.cvsignore
+.deps
+.git
+.gitattributes
+.gitignore
+.gitmodules
+.hg
+.hgignore
+.hgsigs
+.hgtags
+.shelf
+.svn
+CVS
+DEADJOE
+RCS
+_MTN
+_darcs
+{arch}
+);
+## use critic
+
+=over 4
+
+=item my $string = get_default_diff_ignore_regex()
+
+Returns the default diff ignore regex.
+
+=cut
+
+sub get_default_diff_ignore_regex {
+    return $diff_ignore_default_regex;
+}
+
+=item set_default_diff_ignore_regex($string)
+
+Set a regex as the new default diff ignore regex.
+
+=cut
+
+sub set_default_diff_ignore_regex {
+    my ($regex) = @_;
+
+    $diff_ignore_default_regex = $regex;
+}
+
+=item my @array = get_default_tar_ignore_pattern()
+
+Returns the default tar ignore pattern, as an array.
+
+=cut
+
+sub get_default_tar_ignore_pattern {
+    return @tar_ignore_default_pattern;
+}
+
+=item $p = Dpkg::Source::Package->new(filename => $dscfile, options => {})
+
+Creates a new object corresponding to the source package described
+by the file $dscfile.
+
+The options hash supports the following options:
+
+=over 8
+
+=item skip_debianization
+
+If set to 1, do not apply Debian changes on the extracted source package.
+
+=item skip_patches
+
+If set to 1, do not apply Debian-specific patches. This options is
+specific for source packages using format "2.0" and "3.0 (quilt)".
+
+=item require_valid_signature
+
+If set to 1, the check_signature() method will be stricter and will error
+out if the signature can't be verified.
+
+=item copy_orig_tarballs
+
+If set to 1, the extraction will copy the upstream tarballs next the
+target directory. This is useful if you want to be able to rebuild the
+source package after its extraction.
+
+=back
+
+=cut
+
+# Object methods
+sub new {
+    my ($this, %args) = @_;
+    my $class = ref($this) || $this;
+    my $self = {
+        fields => Dpkg::Control->new(type => CTRL_PKG_SRC),
+        options => {},
+        checksums => Dpkg::Checksums->new(),
+    };
+    bless $self, $class;
+    if (exists $args{options}) {
+        $self->{options} = $args{options};
+    }
+    if (exists $args{filename}) {
+        $self->initialize($args{filename});
+        $self->init_options();
+    }
+    return $self;
+}
+
+sub init_options {
+    my ($self) = @_;
+    # Use full ignore list by default
+    # note: this function is not called by V1 packages
+    $self->{options}{diff_ignore_regex} ||= $diff_ignore_default_regex;
+    $self->{options}{diff_ignore_regex} .= '|(?:^|/)debian/source/local-.*$';
+    if (defined $self->{options}{tar_ignore}) {
+        $self->{options}{tar_ignore} = [ @tar_ignore_default_pattern ]
+            unless @{$self->{options}{tar_ignore}};
+    } else {
+        $self->{options}{tar_ignore} = [ @tar_ignore_default_pattern ];
+    }
+    push @{$self->{options}{tar_ignore}}, 'debian/source/local-options',
+         'debian/source/local-patch-header';
+    # Skip debianization while specific to some formats has an impact
+    # on code common to all formats
+    $self->{options}{skip_debianization} ||= 0;
+}
+
+sub initialize {
+    my ($self, $filename) = @_;
+    my ($fn, $dir) = fileparse($filename);
+    error(_g('%s is not the name of a file'), $filename) unless $fn;
+    $self->{basedir} = $dir || './';
+    $self->{filename} = $fn;
+
+    # Read the fields
+    my $fields = Dpkg::Control->new(type => CTRL_PKG_SRC);
+    $fields->load($filename);
+    $self->{fields} = $fields;
+    $self->{is_signed} = $fields->get_option('is_pgp_signed');
+
+    foreach my $f (qw(Source Version Files)) {
+        unless (defined($fields->{$f})) {
+            error(_g('missing critical source control field %s'), $f);
+        }
+    }
+
+    $self->{checksums}->add_from_control($fields, use_files_for_md5 => 1);
+
+    $self->upgrade_object_type(0);
+}
+
+sub upgrade_object_type {
+    my ($self, $update_format) = @_;
+    $update_format //= 1;
+    $self->{fields}{'Format'} = '1.0'
+        unless exists $self->{fields}{'Format'};
+    my $format = $self->{fields}{'Format'};
+
+    if ($format =~ /^([\d\.]+)(?:\s+\((.*)\))?$/) {
+        my ($version, $variant, $major, $minor) = ($1, $2, $1, undef);
+
+        if (defined $variant and $variant ne lc $variant) {
+            error(_g("source package format '%s' is not supported: %s"),
+                  $format, _g('format variant must be in lowercase'));
+        }
+
+        $major =~ s/\.[\d\.]+$//;
+        my $module = "Dpkg::Source::Package::V$major";
+        $module .= '::' . ucfirst $variant if defined $variant;
+        eval "require $module; \$minor = \$${module}::CURRENT_MINOR_VERSION;";
+        $minor //= 0;
+        if ($update_format) {
+            $self->{fields}{'Format'} = "$major.$minor";
+            $self->{fields}{'Format'} .= " ($variant)" if defined $variant;
+        }
+        if ($@) {
+            error(_g("source package format '%s' is not supported: %s"),
+                  $format, $@);
+        }
+        bless $self, $module;
+    } else {
+        error(_g("invalid Format field `%s'"), $format);
+    }
+}
+
+=item $p->get_filename()
+
+Returns the filename of the DSC file.
+
+=cut
+
+sub get_filename {
+    my ($self) = @_;
+    return $self->{basedir} . $self->{filename};
+}
+
+=item $p->get_files()
+
+Returns the list of files referenced by the source package. The filenames
+usually do not have any path information.
+
+=cut
+
+sub get_files {
+    my ($self) = @_;
+    return $self->{checksums}->get_files();
+}
+
+=item $p->check_checksums()
+
+Verify the checksums embedded in the DSC file. It requires the presence of
+the other files constituting the source package. If any inconsistency is
+discovered, it immediately errors out.
+
+=cut
+
+sub check_checksums {
+    my ($self) = @_;
+    my $checksums = $self->{checksums};
+    # add_from_file verify the checksums if they are already existing
+    foreach my $file ($checksums->get_files()) {
+	$checksums->add_from_file($self->{basedir} . $file, key => $file);
+    }
+}
+
+sub get_basename {
+    my ($self, $with_revision) = @_;
+    my $f = $self->{fields};
+    unless (exists $f->{'Source'} and exists $f->{'Version'}) {
+        error(_g('source and version are required to compute the source basename'));
+    }
+    my $v = Dpkg::Version->new($f->{'Version'});
+    my $vs = $v->as_string(omit_epoch => 1, omit_revision => !$with_revision);
+    return $f->{'Source'} . '_' . $vs;
+}
+
+sub find_original_tarballs {
+    my ($self, %opts) = @_;
+    $opts{extension} = compression_get_file_extension_regex()
+        unless exists $opts{extension};
+    $opts{include_main} = 1 unless exists $opts{include_main};
+    $opts{include_supplementary} = 1 unless exists $opts{include_supplementary};
+    my $basename = $self->get_basename();
+    my @tar;
+    foreach my $dir ('.', $self->{basedir}, $self->{options}{origtardir}) {
+        next unless defined($dir) and -d $dir;
+        opendir(my $dir_dh, $dir) or syserr(_g('cannot opendir %s'), $dir);
+        push @tar, map { "$dir/$_" } grep {
+		($opts{include_main} and
+		 /^\Q$basename\E\.orig\.tar\.$opts{extension}$/) or
+		($opts{include_supplementary} and
+		 /^\Q$basename\E\.orig-[[:alnum:]-]+\.tar\.$opts{extension}$/)
+	    } readdir($dir_dh);
+        closedir($dir_dh);
+    }
+    return @tar;
+}
+
+=item $bool = $p->is_signed()
+
+Returns 1 if the DSC files contains an embedded OpenPGP signature.
+Otherwise returns 0.
+
+=cut
+
+sub is_signed {
+    my $self = shift;
+    return $self->{is_signed};
+}
+
+=item $p->check_signature()
+
+Implement the same OpenPGP signature check that dpkg-source does.
+In case of problems, it prints a warning or errors out.
+
+If the object has been created with the "require_valid_signature" option,
+then any problem will result in a fatal error.
+
+=cut
+
+sub check_signature {
+    my ($self) = @_;
+    my $dsc = $self->get_filename();
+    my @exec;
+
+    if (find_command('gpgv2')) {
+        push @exec, 'gpgv2';
+    } elsif (find_command('gpgv')) {
+        push @exec, 'gpgv';
+    } elsif (find_command('gpg2')) {
+        push @exec, 'gpg2', '--no-default-keyring', '-q', '--verify';
+    } elsif (find_command('gpg')) {
+        push @exec, 'gpg', '--no-default-keyring', '-q', '--verify';
+    }
+    if (scalar(@exec)) {
+        if (defined $ENV{HOME} and -r "$ENV{HOME}/.gnupg/trustedkeys.gpg") {
+            push @exec, '--keyring', "$ENV{HOME}/.gnupg/trustedkeys.gpg";
+        }
+        foreach my $vendor_keyring (run_vendor_hook('keyrings')) {
+            if (-r $vendor_keyring) {
+                push @exec, '--keyring', $vendor_keyring;
+            }
+        }
+        push @exec, $dsc;
+
+        my ($stdout, $stderr);
+        spawn(exec => \@exec, wait_child => 1, nocheck => 1,
+              to_string => \$stdout, error_to_string => \$stderr,
+              timeout => 10);
+        if (WIFEXITED($?)) {
+            my $gpg_status = WEXITSTATUS($?);
+            print { *STDERR } "$stdout$stderr" if $gpg_status;
+            if ($gpg_status == 1 or ($gpg_status &&
+                $self->{options}{require_valid_signature}))
+            {
+                error(_g('failed to verify signature on %s'), $dsc);
+            } elsif ($gpg_status) {
+                warning(_g('failed to verify signature on %s'), $dsc);
+            }
+        } else {
+            subprocerr("@exec");
+        }
+    } else {
+        if ($self->{options}{require_valid_signature}) {
+            error(_g("could not verify signature on %s since gpg isn't installed"), $dsc);
+        } else {
+            warning(_g("could not verify signature on %s since gpg isn't installed"), $dsc);
+        }
+    }
+}
+
+sub parse_cmdline_options {
+    my ($self, @opts) = @_;
+    foreach (@opts) {
+        if (not $self->parse_cmdline_option($_)) {
+            warning(_g('%s is not a valid option for %s'), $_, ref($self));
+        }
+    }
+}
+
+sub parse_cmdline_option {
+    return 0;
+}
+
+=item $p->extract($targetdir)
+
+Extracts the source package in the target directory $targetdir. Beware
+that if $targetdir already exists, it will be erased.
+
+=cut
+
+sub extract {
+    my $self = shift;
+    my $newdirectory = $_[0];
+
+    my ($ok, $error) = version_check($self->{fields}{'Version'});
+    error($error) unless $ok;
+
+    # Copy orig tarballs
+    if ($self->{options}{copy_orig_tarballs}) {
+        my $basename = $self->get_basename();
+        my ($dirname, $destdir) = fileparse($newdirectory);
+        $destdir ||= './';
+	my $ext = compression_get_file_extension_regex();
+        foreach my $orig (grep { /^\Q$basename\E\.orig(-[[:alnum:]-]+)?\.tar\.$ext$/ }
+                          $self->get_files())
+        {
+            my $src = File::Spec->catfile($self->{basedir}, $orig);
+            my $dst = File::Spec->catfile($destdir, $orig);
+            if (not check_files_are_the_same($src, $dst, 1)) {
+                system('cp', '--', $src, $dst);
+                subprocerr("cp $src to $dst") if $?;
+            }
+        }
+    }
+
+    # Try extract
+    eval { $self->do_extract(@_) };
+    if ($@) {
+        run_exit_handlers();
+        die $@;
+    }
+
+    # Store format if non-standard so that next build keeps the same format
+    if ($self->{fields}{'Format'} ne '1.0' and
+        not $self->{options}{skip_debianization})
+    {
+        my $srcdir = File::Spec->catdir($newdirectory, 'debian', 'source');
+        my $format_file = File::Spec->catfile($srcdir, 'format');
+	unless (-e $format_file) {
+	    mkdir($srcdir) unless -e $srcdir;
+	    open(my $format_fh, '>', $format_file)
+	        or syserr(_g('cannot write %s'), $format_file);
+	    print { $format_fh } $self->{fields}{'Format'} . "\n";
+	    close($format_fh);
+	}
+    }
+
+    # Make sure debian/rules is executable
+    my $rules = File::Spec->catfile($newdirectory, 'debian', 'rules');
+    my @s = lstat($rules);
+    if (not scalar(@s)) {
+        unless ($! == ENOENT) {
+            syserr(_g('cannot stat %s'), $rules);
+        }
+        warning(_g('%s does not exist'), $rules)
+            unless $self->{options}{skip_debianization};
+    } elsif (-f _) {
+        chmod($s[2] | 0111, $rules)
+            or syserr(_g('cannot make %s executable'), $rules);
+    } else {
+        warning(_g('%s is not a plain file'), $rules);
+    }
+}
+
+sub do_extract {
+    croak 'Dpkg::Source::Package does not know how to unpack a ' .
+          'source package; use one of the subclasses';
+}
+
+# Function used specifically during creation of a source package
+
+sub before_build {
+    my ($self, $dir) = @_;
+}
+
+sub build {
+    my $self = shift;
+    eval { $self->do_build(@_) };
+    if ($@) {
+        run_exit_handlers();
+        die $@;
+    }
+}
+
+sub after_build {
+    my ($self, $dir) = @_;
+}
+
+sub do_build {
+    croak 'Dpkg::Source::Package does not know how to build a ' .
+          'source package; use one of the subclasses';
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+    return (0, 'can_build() has not been overriden');
+}
+
+sub add_file {
+    my ($self, $filename) = @_;
+    my ($fn, $dir) = fileparse($filename);
+    if ($self->{checksums}->has_file($fn)) {
+        croak "tried to add file '$fn' twice";
+    }
+    $self->{checksums}->add_from_file($filename, key => $fn);
+    $self->{checksums}->export_to_control($self->{fields},
+					    use_files_for_md5 => 1);
+}
+
+sub commit {
+    my $self = shift;
+    eval { $self->do_commit(@_) };
+    if ($@) {
+        run_exit_handlers();
+        die $@;
+    }
+}
+
+sub do_commit {
+    my ($self, $dir) = @_;
+    info(_g("'%s' is not supported by the source format '%s'"),
+         'dpkg-source --commit', $self->{fields}{'Format'});
+}
+
+sub write_dsc {
+    my ($self, %opts) = @_;
+    my $fields = $self->{fields};
+
+    foreach my $f (keys %{$opts{override}}) {
+	$fields->{$f} = $opts{override}{$f};
+    }
+
+    unless($opts{nocheck}) {
+        foreach my $f (qw(Source Version)) {
+            unless (defined($fields->{$f})) {
+                error(_g('missing information for critical output field %s'), $f);
+            }
+        }
+        foreach my $f (qw(Maintainer Architecture Standards-Version)) {
+            unless (defined($fields->{$f})) {
+                warning(_g('missing information for output field %s'), $f);
+            }
+        }
+    }
+
+    foreach my $f (keys %{$opts{remove}}) {
+	delete $fields->{$f};
+    }
+
+    my $filename = $opts{filename};
+    unless (defined $filename) {
+        $filename = $self->get_basename(1) . '.dsc';
+    }
+    open(my $dsc_fh, '>', $filename)
+        or syserr(_g('cannot write %s'), $filename);
+    $fields->apply_substvars($opts{substvars});
+    $fields->output($dsc_fh);
+    close($dsc_fh);
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New functions: get_default_diff_ignore_regex(), set_default_diff_ignore_regex(),
+get_default_tar_ignore_pattern()
+
+Deprecated variables: $diff_ignore_default_regexp, @tar_ignore_default_pattern
+
+=head1 AUTHOR
+
+Raphaël Hertzog, E<lt>hertzog@debian.orgE<gt>
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V1.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V1.pm
new file mode 100644
index 0000000..d0dfa97
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V1.pm
@@ -0,0 +1,402 @@
+# Copyright © 2008-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V1;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Source::Package);
+
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Compression;
+use Dpkg::Source::Archive;
+use Dpkg::Source::Patch;
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
+use Dpkg::Source::Functions qw(erasedir);
+use Dpkg::Source::Package::V3::Native;
+
+use POSIX qw(:errno_h);
+use Cwd;
+use File::Basename;
+use File::Temp qw(tempfile);
+use File::Spec;
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub init_options {
+    my ($self) = @_;
+    # Don't call $self->SUPER::init_options() on purpose, V1.0 has no
+    # ignore by default
+    if ($self->{options}{diff_ignore_regex}) {
+	$self->{options}{diff_ignore_regex} .= '|(?:^|/)debian/source/local-.*$';
+    } else {
+	$self->{options}{diff_ignore_regex} = '(?:^|/)debian/source/local-.*$';
+    }
+    push @{$self->{options}{tar_ignore}}, 'debian/source/local-options',
+         'debian/source/local-patch-header';
+    $self->{options}{sourcestyle} ||= 'X';
+    $self->{options}{skip_debianization} ||= 0;
+    $self->{options}{abort_on_upstream_changes} ||= 0;
+}
+
+sub parse_cmdline_option {
+    my ($self, $opt) = @_;
+    my $o = $self->{options};
+    if ($opt =~ m/^-s([akpursnAKPUR])$/) {
+        warning(_g('-s%s option overrides earlier -s%s option'), $1,
+                $o->{sourcestyle}) if $o->{sourcestyle} ne 'X';
+        $o->{sourcestyle} = $1;
+        $o->{copy_orig_tarballs} = 0 if $1 eq 'n'; # Extract option -sn
+        return 1;
+    } elsif ($opt =~ m/^--skip-debianization$/) {
+        $o->{skip_debianization} = 1;
+        return 1;
+    } elsif ($opt =~ m/^--abort-on-upstream-changes$/) {
+        $o->{abort_on_upstream_changes} = 1;
+        return 1;
+    }
+    return 0;
+}
+
+sub do_extract {
+    my ($self, $newdirectory) = @_;
+    my $sourcestyle = $self->{options}{sourcestyle};
+    my $fields = $self->{fields};
+
+    $sourcestyle =~ y/X/p/;
+    unless ($sourcestyle =~ m/[pun]/) {
+	usageerr(_g('source handling style -s%s not allowed with -x'),
+	         $sourcestyle);
+    }
+
+    my $dscdir = $self->{basedir};
+
+    my $basename = $self->get_basename();
+    my $basenamerev = $self->get_basename(1);
+
+    # V1.0 only supports gzip compression
+    my ($tarfile, $difffile);
+    my $tarsign;
+    foreach my $file ($self->get_files()) {
+	if ($file =~ /^(?:\Q$basename\E\.orig|\Q$basenamerev\E)\.tar\.gz$/) {
+            error(_g('multiple tarfiles in v1.0 source package')) if $tarfile;
+            $tarfile = $file;
+        } elsif ($file =~ /^\Q$basename\E\.orig\.tar\.gz\.asc$/) {
+            $tarsign = $file;
+	} elsif ($file =~ /^\Q$basenamerev\E\.diff\.gz$/) {
+	    $difffile = $file;
+	} else {
+	    error(_g('unrecognized file for a %s source package: %s'),
+                  'v1.0', $file);
+	}
+    }
+
+    error(_g('no tarfile in Files field')) unless $tarfile;
+    my $native = $difffile ? 0 : 1;
+    if ($native and ($tarfile =~ /\.orig\.tar\.gz$/)) {
+        warning(_g('native package with .orig.tar'));
+        $native = 0; # V3::Native doesn't handle orig.tar
+    }
+
+    if ($native) {
+        Dpkg::Source::Package::V3::Native::do_extract($self, $newdirectory);
+    } else {
+        my $expectprefix = $newdirectory;
+        $expectprefix .= '.orig';
+
+        erasedir($newdirectory);
+        if (-e $expectprefix) {
+            rename($expectprefix, "$newdirectory.tmp-keep")
+                or syserr(_g("unable to rename `%s' to `%s'"), $expectprefix,
+                          "$newdirectory.tmp-keep");
+        }
+
+        info(_g('unpacking %s'), $tarfile);
+        my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
+        $tar->extract($expectprefix);
+
+        if ($sourcestyle =~ /u/) {
+            # -su: keep .orig directory unpacked
+            if (-e "$newdirectory.tmp-keep") {
+                error(_g('unable to keep orig directory (already exists)'));
+            }
+            system('cp', '-ar', '--', $expectprefix, "$newdirectory.tmp-keep");
+            subprocerr("cp $expectprefix to $newdirectory.tmp-keep") if $?;
+        }
+
+	rename($expectprefix, $newdirectory)
+	    or syserr(_g('failed to rename newly-extracted %s to %s'),
+	              $expectprefix, $newdirectory);
+
+	# rename the copied .orig directory
+	if (-e "$newdirectory.tmp-keep") {
+	    rename("$newdirectory.tmp-keep", $expectprefix)
+	        or syserr(_g('failed to rename saved %s to %s'),
+	                  "$newdirectory.tmp-keep", $expectprefix);
+        }
+    }
+
+    if ($difffile and not $self->{options}{skip_debianization}) {
+        my $patch = "$dscdir$difffile";
+	info(_g('applying %s'), $difffile);
+	my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
+	my $analysis = $patch_obj->apply($newdirectory, force_timestamp => 1);
+	my @files = grep { ! m{^\Q$newdirectory\E/debian/} }
+		    sort keys %{$analysis->{filepatched}};
+	info(_g('upstream files that have been modified: %s'),
+	     "\n " . join("\n ", @files)) if scalar @files;
+    }
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+
+    # As long as we can use gzip, we can do it as we have
+    # native packages as fallback
+    return (0, _g('only supports gzip compression'))
+        unless $self->{options}{compression} eq 'gzip';
+    return 1;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    my $sourcestyle = $self->{options}{sourcestyle};
+    my @argv = @{$self->{options}{ARGV}};
+    my @tar_ignore = map { "--exclude=$_" } @{$self->{options}{tar_ignore}};
+    my $diff_ignore_regex = $self->{options}{diff_ignore_regex};
+
+    if (scalar(@argv) > 1) {
+        usageerr(_g('-b takes at most a directory and an orig source ' .
+                    'argument (with v1.0 source package)'));
+    }
+
+    $sourcestyle =~ y/X/A/;
+    unless ($sourcestyle =~ m/[akpursnAKPUR]/) {
+        usageerr(_g('source handling style -s%s not allowed with -b'),
+                 $sourcestyle);
+    }
+
+    my $sourcepackage = $self->{fields}{'Source'};
+    my $basenamerev = $self->get_basename(1);
+    my $basename = $self->get_basename();
+    my $basedirname = $basename;
+    $basedirname =~ s/_/-/;
+
+    # Try to find a .orig tarball for the package
+    my $origdir = "$dir.orig";
+    my $origtargz = $self->get_basename() . '.orig.tar.gz';
+    if (-e $origtargz) {
+        unless (-f $origtargz) {
+            error(_g("packed orig `%s' exists but is not a plain file"), $origtargz);
+        }
+    } else {
+        $origtargz = undef;
+    }
+
+    if (@argv) {
+	# We have a second-argument <orig-dir> or <orig-targz>, check what it
+	# is to decide the mode to use
+        my $origarg = shift(@argv);
+        if (length($origarg)) {
+            stat($origarg)
+                or syserr(_g('cannot stat orig argument %s'), $origarg);
+            if (-d _) {
+                $origdir = File::Spec->catdir($origarg);
+
+                $sourcestyle =~ y/aA/rR/;
+                unless ($sourcestyle =~ m/[ursURS]/) {
+                    error(_g('orig argument is unpacked but source handling ' .
+                             'style -s%s calls for packed (.orig.tar.<ext>)'),
+                          $sourcestyle);
+                }
+            } elsif (-f _) {
+                $origtargz = $origarg;
+                $sourcestyle =~ y/aA/pP/;
+                unless ($sourcestyle =~ m/[kpsKPS]/) {
+                    error(_g('orig argument is packed but source handling ' .
+                             'style -s%s calls for unpacked (.orig/)'),
+                          $sourcestyle);
+                }
+            } else {
+                error(_g('orig argument %s is not a plain file or directory'),
+                      $origarg);
+            }
+        } else {
+            $sourcestyle =~ y/aA/nn/;
+            unless ($sourcestyle =~ m/n/) {
+                error(_g('orig argument is empty (means no orig, no diff) ' .
+                         'but source handling style -s%s wants something'),
+                      $sourcestyle);
+            }
+        }
+    } elsif ($sourcestyle =~ m/[aA]/) {
+	# We have no explicit <orig-dir> or <orig-targz>, try to use
+	# a .orig tarball first, then a .orig directory and fall back to
+	# creating a native .tar.gz
+	if ($origtargz) {
+	    $sourcestyle =~ y/aA/pP/; # .orig.tar.<ext>
+	} else {
+	    if (stat($origdir)) {
+		unless (-d _) {
+                    error(_g("unpacked orig `%s' exists but is not a directory"),
+		          $origdir);
+                }
+		$sourcestyle =~ y/aA/rR/; # .orig directory
+	    } elsif ($! != ENOENT) {
+		syserr(_g("unable to stat putative unpacked orig `%s'"), $origdir);
+	    } else {
+		$sourcestyle =~ y/aA/nn/; # Native tar.gz
+	    }
+	}
+    }
+
+    my ($dirname, $dirbase) = fileparse($dir);
+    if ($dirname ne $basedirname) {
+	warning(_g("source directory '%s' is not <sourcepackage>" .
+	           "-<upstreamversion> '%s'"), $dir, $basedirname);
+    }
+
+    my ($tarname, $tardirname, $tardirbase);
+    if ($sourcestyle ne 'n') {
+	my ($origdirname, $origdirbase) = fileparse($origdir);
+
+        if ($origdirname ne "$basedirname.orig") {
+            warning(_g('.orig directory name %s is not <package>' .
+	               '-<upstreamversion> (wanted %s)'),
+	            $origdirname, "$basedirname.orig");
+        }
+        $tardirbase = $origdirbase;
+        $tardirname = $origdirname;
+
+	$tarname = $origtargz || "$basename.orig.tar.gz";
+	unless ($tarname =~ /\Q$basename\E\.orig\.tar\.gz/) {
+	    warning(_g('.orig.tar name %s is not <package>_<upstreamversion>' .
+	               '.orig.tar (wanted %s)'),
+	            $tarname, "$basename.orig.tar.gz");
+	}
+    }
+
+    if ($sourcestyle eq 'n') {
+        $self->{options}{ARGV} = []; # ensure we have no error
+        Dpkg::Source::Package::V3::Native::do_build($self, $dir);
+    } elsif ($sourcestyle =~ m/[nurUR]/) {
+        if (stat($tarname)) {
+            unless ($sourcestyle =~ m/[nUR]/) {
+		error(_g("tarfile `%s' already exists, not overwriting, " .
+		         'giving up; use -sU or -sR to override'), $tarname);
+            }
+        } elsif ($! != ENOENT) {
+	    syserr(_g("unable to check for existence of `%s'"), $tarname);
+        }
+
+	info(_g('building %s in %s'),
+	     $sourcepackage, $tarname);
+
+	my ($ntfh, $newtar) = tempfile("$tarname.new.XXXXXX",
+				       DIR => getcwd(), UNLINK => 0);
+	my $tar = Dpkg::Source::Archive->new(filename => $newtar,
+		    compression => compression_guess_from_filename($tarname),
+		    compression_level => $self->{options}{comp_level});
+	$tar->create(options => \@tar_ignore, chdir => $tardirbase);
+	$tar->add_directory($tardirname);
+	$tar->finish();
+	rename($newtar, $tarname)
+	    or syserr(_g("unable to rename `%s' (newly created) to `%s'"),
+	              $newtar, $tarname);
+	chmod(0666 &~ umask(), $tarname)
+	    or syserr(_g("unable to change permission of `%s'"), $tarname);
+    } else {
+	info(_g('building %s using existing %s'),
+	     $sourcepackage, $tarname);
+    }
+
+    $self->add_file($tarname) if $tarname;
+
+    if ($sourcestyle =~ m/[kpKP]/) {
+        if (stat($origdir)) {
+            unless ($sourcestyle =~ m/[KP]/) {
+                error(_g("orig dir `%s' already exists, not overwriting, ".
+                         'giving up; use -sA, -sK or -sP to override'),
+                      $origdir);
+            }
+            push_exit_handler(sub { erasedir($origdir) });
+            erasedir($origdir);
+            pop_exit_handler();
+        } elsif ($! != ENOENT) {
+            syserr(_g("unable to check for existence of orig dir `%s'"),
+                    $origdir);
+        }
+
+	my $tar = Dpkg::Source::Archive->new(filename => $origtargz);
+	$tar->extract($origdir);
+    }
+
+    my $ur; # Unrepresentable changes
+    if ($sourcestyle =~ m/[kpursKPUR]/) {
+	my $diffname = "$basenamerev.diff.gz";
+	info(_g('building %s in %s'),
+	     $sourcepackage, $diffname);
+	my ($ndfh, $newdiffgz) = tempfile("$diffname.new.XXXXXX",
+					DIR => getcwd(), UNLINK => 0);
+        push_exit_handler(sub { unlink($newdiffgz) });
+        my $diff = Dpkg::Source::Patch->new(filename => $newdiffgz,
+                                            compression => 'gzip');
+        $diff->create();
+        $diff->add_diff_directory($origdir, $dir,
+                basedirname => $basedirname,
+                diff_ignore_regex => $diff_ignore_regex,
+                options => []); # Force empty set of options to drop the
+                                # default -p option
+        $diff->finish() || $ur++;
+        pop_exit_handler();
+
+	my $analysis = $diff->analyze($origdir);
+	my @files = grep { ! m{^debian/} } map { s{^[^/]+/+}{}; $_ }
+		    sort keys %{$analysis->{filepatched}};
+	if (scalar @files) {
+	    warning(_g('the diff modifies the following upstream files: %s'),
+	            "\n " . join("\n ", @files));
+	    info(_g("use the '3.0 (quilt)' format to have separate and " .
+	            'documented changes to upstream files, see dpkg-source(1)'));
+	    error(_g('aborting due to --abort-on-upstream-changes'))
+		if $self->{options}{abort_on_upstream_changes};
+	}
+
+	rename($newdiffgz, $diffname)
+	    or syserr(_g("unable to rename `%s' (newly created) to `%s'"),
+	              $newdiffgz, $diffname);
+	chmod(0666 &~ umask(), $diffname)
+	    or syserr(_g("unable to change permission of `%s'"), $diffname);
+
+	$self->add_file($diffname);
+    }
+
+    if ($sourcestyle =~ m/[prPR]/) {
+        erasedir($origdir);
+    }
+
+    if ($ur) {
+        printf { *STDERR } _g('%s: unrepresentable changes to source') . "\n",
+               $Dpkg::PROGNAME;
+        exit(1);
+    }
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm
new file mode 100644
index 0000000..c5babfb
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V2.pm
@@ -0,0 +1,767 @@
+# Copyright © 2008-2011 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V2;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Source::Package);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::File;
+use Dpkg::Compression;
+use Dpkg::Source::Archive;
+use Dpkg::Source::Patch;
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
+use Dpkg::Source::Functions qw(erasedir is_binary fs_time);
+use Dpkg::Vendor qw(run_vendor_hook);
+use Dpkg::Control;
+use Dpkg::Changelog::Parse;
+
+use POSIX qw(:errno_h);
+use Cwd;
+use File::Basename;
+use File::Temp qw(tempfile tempdir);
+use File::Path;
+use File::Spec;
+use File::Find;
+use File::Copy;
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub init_options {
+    my ($self) = @_;
+    $self->SUPER::init_options();
+    $self->{options}{include_removal} = 0
+        unless exists $self->{options}{include_removal};
+    $self->{options}{include_timestamp} = 0
+        unless exists $self->{options}{include_timestamp};
+    $self->{options}{include_binaries} = 0
+        unless exists $self->{options}{include_binaries};
+    $self->{options}{preparation} = 1
+        unless exists $self->{options}{preparation};
+    $self->{options}{skip_patches} = 0
+        unless exists $self->{options}{skip_patches};
+    $self->{options}{unapply_patches} = 'auto'
+        unless exists $self->{options}{unapply_patches};
+    $self->{options}{skip_debianization} = 0
+        unless exists $self->{options}{skip_debianization};
+    $self->{options}{create_empty_orig} = 0
+        unless exists $self->{options}{create_empty_orig};
+    $self->{options}{auto_commit} = 0
+        unless exists $self->{options}{auto_commit};
+}
+
+sub parse_cmdline_option {
+    my ($self, $opt) = @_;
+    if ($opt =~ /^--include-removal$/) {
+        $self->{options}{include_removal} = 1;
+        return 1;
+    } elsif ($opt =~ /^--include-timestamp$/) {
+        $self->{options}{include_timestamp} = 1;
+        return 1;
+    } elsif ($opt =~ /^--include-binaries$/) {
+        $self->{options}{include_binaries} = 1;
+        return 1;
+    } elsif ($opt =~ /^--no-preparation$/) {
+        $self->{options}{preparation} = 0;
+        return 1;
+    } elsif ($opt =~ /^--skip-patches$/) {
+        $self->{options}{skip_patches} = 1;
+        return 1;
+    } elsif ($opt =~ /^--unapply-patches$/) {
+        $self->{options}{unapply_patches} = 'yes';
+        return 1;
+    } elsif ($opt =~ /^--no-unapply-patches$/) {
+        $self->{options}{unapply_patches} = 'no';
+        return 1;
+    } elsif ($opt =~ /^--skip-debianization$/) {
+        $self->{options}{skip_debianization} = 1;
+        return 1;
+    } elsif ($opt =~ /^--create-empty-orig$/) {
+        $self->{options}{create_empty_orig} = 1;
+        return 1;
+    } elsif ($opt =~ /^--abort-on-upstream-changes$/) {
+        $self->{options}{auto_commit} = 0;
+        return 1;
+    } elsif ($opt =~ /^--auto-commit$/) {
+        $self->{options}{auto_commit} = 1;
+        return 1;
+    }
+    return 0;
+}
+
+sub do_extract {
+    my ($self, $newdirectory) = @_;
+    my $fields = $self->{fields};
+
+    my $dscdir = $self->{basedir};
+
+    my $basename = $self->get_basename();
+    my $basenamerev = $self->get_basename(1);
+
+    my ($tarfile, $debianfile, %origtar, %seen);
+    my ($tarsign, %origtarsign);
+    my $re_ext = compression_get_file_extension_regex();
+    foreach my $file ($self->get_files()) {
+        my $uncompressed = $file;
+        $uncompressed =~ s/\.$re_ext$/.*/;
+        $uncompressed =~ s/\.$re_ext\.asc$/.*.asc/;
+        error(_g('duplicate files in %s source package: %s'), 'v2.0',
+              $uncompressed) if $seen{$uncompressed};
+        $seen{$uncompressed} = 1;
+        if ($file =~ /^\Q$basename\E\.orig\.tar\.$re_ext$/) {
+            $tarfile = $file;
+        } elsif ($file =~ /^\Q$basename\E\.orig\.tar\.$re_ext\.asc$/) {
+            $tarsign = $file;
+        } elsif ($file =~ /^\Q$basename\E\.orig-([[:alnum:]-]+)\.tar\.$re_ext$/) {
+            $origtar{$1} = $file;
+        } elsif ($file =~ /^\Q$basename\E\.orig-([[:alnum:]-]+)\.tar\.$re_ext\.asc$/) {
+            $origtarsign{$1} = $file;
+        } elsif ($file =~ /^\Q$basenamerev\E\.debian\.tar\.$re_ext$/) {
+            $debianfile = $file;
+        } else {
+            error(_g('unrecognized file for a %s source package: %s'),
+            'v2.0', $file);
+        }
+    }
+
+    unless ($tarfile and $debianfile) {
+        error(_g('missing orig.tar or debian.tar file in v2.0 source package'));
+    }
+    if ($tarsign and $tarfile ne substr $tarsign, 0, -4) {
+        error(_g('mismatched orig.tar %s for signature %s in source package'),
+              $tarfile, $tarsign);
+    }
+    foreach my $name (keys %origtarsign) {
+        error(_g('missing addon orig.tar for signature %s in source package'),
+              $origtarsign{$name})
+            if not exists $origtar{$name};
+        error(_g('mismatched addon orig.tar %s for signature %s in source package'),
+              $origtar{$name}, $origtarsign{$name})
+            if $origtar{$name} ne substr $origtarsign{$name}, 0, -4;
+    }
+
+    erasedir($newdirectory);
+
+    # Extract main tarball
+    info(_g('unpacking %s'), $tarfile);
+    my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
+    $tar->extract($newdirectory, no_fixperms => 1,
+                  options => [ '--anchored', '--no-wildcards-match-slash',
+                               '--exclude', '*/.pc', '--exclude', '.pc' ]);
+    # The .pc exclusion is only needed for 3.0 (quilt) and to avoid
+    # having an upstream tarball provide a directory with symlinks
+    # that would be blindly followed when applying the patches
+
+    # Extract additional orig tarballs
+    foreach my $subdir (keys %origtar) {
+        my $file = $origtar{$subdir};
+        info(_g('unpacking %s'), $file);
+        if (-e "$newdirectory/$subdir") {
+            warning(_g("required removal of `%s' installed by original tarball"), $subdir);
+            erasedir("$newdirectory/$subdir");
+        }
+        $tar = Dpkg::Source::Archive->new(filename => "$dscdir$file");
+        $tar->extract("$newdirectory/$subdir", no_fixperms => 1);
+    }
+
+    # Stop here if debianization is not wanted
+    return if $self->{options}{skip_debianization};
+
+    # Extract debian tarball after removing the debian directory
+    info(_g('unpacking %s'), $debianfile);
+    erasedir("$newdirectory/debian");
+    # Exclude existing symlinks from extraction of debian.tar.gz as we
+    # don't want to overwrite something outside of $newdirectory due to a
+    # symlink
+    my @exclude_symlinks;
+    my $wanted = sub {
+        return if not -l $_;
+        my $fn = File::Spec->abs2rel($_, $newdirectory);
+        push @exclude_symlinks, '--exclude', $fn;
+    };
+    find({ wanted => $wanted, no_chdir => 1 }, $newdirectory);
+    $tar = Dpkg::Source::Archive->new(filename => "$dscdir$debianfile");
+    $tar->extract($newdirectory, in_place => 1,
+                  options => [ '--anchored', '--no-wildcards',
+                  @exclude_symlinks ]);
+
+    # Apply patches (in a separate method as it might be overriden)
+    $self->apply_patches($newdirectory, usage => 'unpack')
+        unless $self->{options}{skip_patches};
+}
+
+sub get_autopatch_name {
+    return 'zz_debian-diff-auto';
+}
+
+sub get_patches {
+    my ($self, $dir, %opts) = @_;
+    $opts{skip_auto} //= 0;
+    my @patches;
+    my $pd = "$dir/debian/patches";
+    my $auto_patch = $self->get_autopatch_name();
+    if (-d $pd) {
+        opendir(my $dir_dh, $pd) or syserr(_g('cannot opendir %s'), $pd);
+        foreach my $patch (sort readdir($dir_dh)) {
+            # patches match same rules as run-parts
+            next unless $patch =~ /^[\w-]+$/ and -f "$pd/$patch";
+            next if $opts{skip_auto} and $patch eq $auto_patch;
+            push @patches, $patch;
+        }
+        closedir($dir_dh);
+    }
+    return @patches;
+}
+
+sub apply_patches {
+    my ($self, $dir, %opts) = @_;
+    $opts{skip_auto} //= 0;
+    my @patches = $self->get_patches($dir, %opts);
+    return unless scalar(@patches);
+    my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied');
+    open(my $applied_fh, '>', $applied)
+        or syserr(_g('cannot write %s'), $applied);
+    print { $applied_fh } "# During $opts{usage}\n";
+    my $timestamp = fs_time($applied);
+    foreach my $patch ($self->get_patches($dir, %opts)) {
+        my $path = File::Spec->catfile($dir, 'debian', 'patches', $patch);
+        info(_g('applying %s'), $patch) unless $opts{skip_auto};
+        my $patch_obj = Dpkg::Source::Patch->new(filename => $path);
+        $patch_obj->apply($dir, force_timestamp => 1,
+                          timestamp => $timestamp,
+                          add_options => [ '-E' ]);
+        print { $applied_fh } "$patch\n";
+    }
+    close($applied_fh);
+}
+
+sub unapply_patches {
+    my ($self, $dir, %opts) = @_;
+    my @patches = reverse($self->get_patches($dir, %opts));
+    return unless scalar(@patches);
+    my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied');
+    my $timestamp = fs_time($applied);
+    foreach my $patch (@patches) {
+        my $path = File::Spec->catfile($dir, 'debian', 'patches', $patch);
+        info(_g('unapplying %s'), $patch) unless $opts{quiet};
+        my $patch_obj = Dpkg::Source::Patch->new(filename => $path);
+        $patch_obj->apply($dir, force_timestamp => 1, verbose => 0,
+                          timestamp => $timestamp,
+                          add_options => [ '-E', '-R' ]);
+    }
+    unlink($applied);
+}
+
+sub upstream_tarball_template {
+    my ($self) = @_;
+    my $ext = '{' . join(',',
+        sort map {
+            compression_get_property($_, 'file_ext')
+        } compression_get_list()) . '}';
+    return '../' . $self->get_basename() . ".orig.tar.$ext";
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+    return 1 if $self->find_original_tarballs(include_supplementary => 0);
+    return 1 if $self->{options}{create_empty_orig} and
+                $self->find_original_tarballs(include_main => 0);
+    return (0, sprintf(_g('no upstream tarball found at %s'),
+                       $self->upstream_tarball_template()));
+}
+
+sub before_build {
+    my ($self, $dir) = @_;
+    $self->check_patches_applied($dir) if $self->{options}{preparation};
+}
+
+sub after_build {
+    my ($self, $dir) = @_;
+    my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied');
+    my $reason = '';
+    if (-e $applied) {
+        open(my $applied_fh, '<', $applied)
+            or syserr(_g('cannot read %s'), $applied);
+        $reason = <$applied_fh>;
+        close($applied_fh);
+    }
+    my $opt_unapply = $self->{options}{unapply_patches};
+    if (($opt_unapply eq 'auto' and $reason =~ /^# During preparation/) or
+        $opt_unapply eq 'yes') {
+        $self->unapply_patches($dir);
+    }
+}
+
+sub prepare_build {
+    my ($self, $dir) = @_;
+    $self->{diff_options} = {
+        diff_ignore_regex => $self->{options}{diff_ignore_regex} .
+                             '|(^|/)debian/patches/.dpkg-source-applied$',
+        include_removal => $self->{options}{include_removal},
+        include_timestamp => $self->{options}{include_timestamp},
+        use_dev_null => 1,
+    };
+    push @{$self->{options}{tar_ignore}}, 'debian/patches/.dpkg-source-applied';
+    $self->check_patches_applied($dir) if $self->{options}{preparation};
+    if ($self->{options}{create_empty_orig} and
+        not $self->find_original_tarballs(include_supplementary => 0))
+    {
+        # No main orig.tar, create a dummy one
+        my $filename = $self->get_basename() . '.orig.tar.' .
+                       $self->{options}{comp_ext};
+        my $tar = Dpkg::Source::Archive->new(filename => $filename);
+        $tar->create();
+        $tar->finish();
+    }
+}
+
+sub check_patches_applied {
+    my ($self, $dir) = @_;
+    my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied');
+    unless (-e $applied) {
+        info(_g('patches are not applied, applying them now'));
+        $self->apply_patches($dir, usage => 'preparation');
+    }
+}
+
+sub generate_patch {
+    my ($self, $dir, %opts) = @_;
+    my ($dirname, $updir) = fileparse($dir);
+    my $basedirname = $self->get_basename();
+    $basedirname =~ s/_/-/;
+
+    # Identify original tarballs
+    my ($tarfile, %origtar);
+    my $comp_ext_regex = compression_get_file_extension_regex();
+    my @origtarballs;
+    foreach (sort $self->find_original_tarballs()) {
+        if (/\.orig\.tar\.$comp_ext_regex$/) {
+            if (defined($tarfile)) {
+                error(_g('several orig.tar files found (%s and %s) but only ' .
+                         'one is allowed'), $tarfile, $_);
+            }
+            $tarfile = $_;
+            push @origtarballs, $_;
+            $self->add_file($_);
+        } elsif (/\.orig-([[:alnum:]-]+)\.tar\.$comp_ext_regex$/) {
+            $origtar{$1} = $_;
+            push @origtarballs, $_;
+            $self->add_file($_);
+        }
+    }
+
+    error(_g('no upstream tarball found at %s'),
+          $self->upstream_tarball_template()) unless $tarfile;
+
+    if ($opts{usage} eq 'build') {
+        info(_g('building %s using existing %s'),
+             $self->{fields}{'Source'}, "@origtarballs");
+    }
+
+    # Unpack a second copy for comparison
+    my $tmp = tempdir("$dirname.orig.XXXXXX", DIR => $updir);
+    push_exit_handler(sub { erasedir($tmp) });
+
+    # Extract main tarball
+    my $tar = Dpkg::Source::Archive->new(filename => $tarfile);
+    $tar->extract($tmp);
+
+    # Extract additional orig tarballs
+    foreach my $subdir (keys %origtar) {
+        my $file = $origtar{$subdir};
+        $tar = Dpkg::Source::Archive->new(filename => $file);
+        $tar->extract("$tmp/$subdir");
+    }
+
+    # Copy over the debian directory
+    erasedir("$tmp/debian");
+    system('cp', '-a', '--', "$dir/debian", "$tmp/");
+    subprocerr(_g('copy of the debian directory')) if $?;
+
+    # Apply all patches except the last automatic one
+    $opts{skip_auto} //= 0;
+    $self->apply_patches($tmp, skip_auto => $opts{skip_auto}, usage => 'build');
+
+    # Create a patch
+    my ($difffh, $tmpdiff) = tempfile($self->get_basename(1) . '.diff.XXXXXX',
+                                      DIR => File::Spec->tmpdir(), UNLINK => 0);
+    push_exit_handler(sub { unlink($tmpdiff) });
+    my $diff = Dpkg::Source::Patch->new(filename => $tmpdiff,
+                                        compression => 'none');
+    $diff->create();
+    if ($opts{header_from} and -e $opts{header_from}) {
+        my $header_from = Dpkg::Source::Patch->new(
+            filename => $opts{header_from});
+        my $analysis = $header_from->analyze($dir, verbose => 0);
+        $diff->set_header($analysis->{patchheader});
+    } else {
+        $diff->set_header($self->get_patch_header($dir));
+    }
+    $diff->add_diff_directory($tmp, $dir, basedirname => $basedirname,
+            %{$self->{diff_options}},
+            handle_binary_func => $opts{handle_binary},
+            order_from => $opts{order_from});
+    error(_g('unrepresentable changes to source')) if not $diff->finish();
+
+    if (-s $tmpdiff) {
+        info(_g('local changes detected, the modified files are:'));
+        my $analysis = $diff->analyze($dir, verbose => 0);
+        foreach my $fn (sort keys %{$analysis->{filepatched}}) {
+            print " $fn\n";
+        }
+    }
+
+    # Remove the temporary directory
+    erasedir($tmp);
+    pop_exit_handler();
+    pop_exit_handler();
+
+    return $tmpdiff;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    my @argv = @{$self->{options}{ARGV}};
+    if (scalar(@argv)) {
+        usageerr(_g("-b takes only one parameter with format `%s'"),
+                 $self->{fields}{'Format'});
+    }
+    $self->prepare_build($dir);
+
+    my $include_binaries = $self->{options}{include_binaries};
+    my @tar_ignore = map { "--exclude=$_" } @{$self->{options}{tar_ignore}};
+
+    my $sourcepackage = $self->{fields}{'Source'};
+    my $basenamerev = $self->get_basename(1);
+
+    # Check if the debian directory contains unwanted binary files
+    my $binaryfiles = Dpkg::Source::Package::V2::BinaryFiles->new($dir);
+    my $unwanted_binaries = 0;
+    my $check_binary = sub {
+        if (-f $_ and is_binary($_)) {
+            my $fn = File::Spec->abs2rel($_, $dir);
+            $binaryfiles->new_binary_found($fn);
+            unless ($include_binaries or $binaryfiles->binary_is_allowed($fn)) {
+                errormsg(_g('unwanted binary file: %s'), $fn);
+                $unwanted_binaries++;
+            }
+        }
+    };
+    my $tar_ignore_glob = '{' . join(',',
+        map {
+            my $copy = $_;
+            $copy =~ s/,/\\,/g;
+            $copy;
+        } @{$self->{options}{tar_ignore}}) . '}';
+    my $filter_ignore = sub {
+        # Filter out files that are not going to be included in the debian
+        # tarball due to ignores.
+        my %exclude;
+        my $reldir = File::Spec->abs2rel($File::Find::dir, $dir);
+        my $cwd = getcwd();
+        # Apply the pattern both from the top dir and from the inspected dir
+        chdir($dir) or syserr(_g("unable to chdir to `%s'"), $dir);
+        $exclude{$_} = 1 foreach glob($tar_ignore_glob);
+        chdir($cwd) or syserr(_g("unable to chdir to `%s'"), $cwd);
+        chdir($File::Find::dir)
+            or syserr(_g("unable to chdir to `%s'"), $File::Find::dir);
+        $exclude{$_} = 1 foreach glob($tar_ignore_glob);
+        chdir($cwd) or syserr(_g("unable to chdir to `%s'"), $cwd);
+        my @result;
+        foreach my $fn (@_) {
+            unless (exists $exclude{$fn} or exists $exclude{"$reldir/$fn"}) {
+                push @result, $fn;
+            }
+        }
+        return @result;
+    };
+    find({ wanted => $check_binary, preprocess => $filter_ignore,
+           no_chdir => 1 }, File::Spec->catdir($dir, 'debian'));
+    error(P_('detected %d unwanted binary file (add it in ' .
+             'debian/source/include-binaries to allow its inclusion).',
+             'detected %d unwanted binary files (add them in ' .
+             'debian/source/include-binaries to allow their inclusion).',
+             $unwanted_binaries), $unwanted_binaries)
+         if $unwanted_binaries;
+
+    # Handle modified binary files detected by the auto-patch generation
+    my $handle_binary = sub {
+        my ($self, $old, $new) = @_;
+        my $relfn = File::Spec->abs2rel($new, $dir);
+        $binaryfiles->new_binary_found($relfn);
+        unless ($include_binaries or $binaryfiles->binary_is_allowed($relfn)) {
+            errormsg(_g('cannot represent change to %s: %s'), $relfn,
+                     _g('binary file contents changed'));
+            errormsg(_g('add %s in debian/source/include-binaries if you want ' .
+                        'to store the modified binary in the debian tarball'),
+                     $relfn);
+            $self->register_error();
+        }
+    };
+
+    # Create a patch
+    my $autopatch = File::Spec->catfile($dir, 'debian', 'patches',
+                                        $self->get_autopatch_name());
+    my $tmpdiff = $self->generate_patch($dir, order_from => $autopatch,
+                                        header_from => $autopatch,
+                                        handle_binary => $handle_binary,
+                                        skip_auto => $self->{options}{auto_commit},
+                                        usage => 'build');
+    unless (-z $tmpdiff or $self->{options}{auto_commit}) {
+        info(_g('you can integrate the local changes with %s'),
+             'dpkg-source --commit');
+        error(_g('aborting due to unexpected upstream changes, see %s'),
+              $tmpdiff);
+    }
+    push_exit_handler(sub { unlink($tmpdiff) });
+    $binaryfiles->update_debian_source_include_binaries() if $include_binaries;
+
+    # Install the diff as the new autopatch
+    if ($self->{options}{auto_commit}) {
+        mkpath(File::Spec->catdir($dir, 'debian', 'patches'));
+        $autopatch = $self->register_patch($dir, $tmpdiff,
+                                           $self->get_autopatch_name());
+        info(_g('local changes have been recorded in a new patch: %s'),
+             $autopatch) if -e $autopatch;
+        rmdir(File::Spec->catdir($dir, 'debian', 'patches')); # No check on purpose
+    }
+    unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff);
+    pop_exit_handler();
+
+    # Create the debian.tar
+    my $debianfile = "$basenamerev.debian.tar." . $self->{options}{comp_ext};
+    info(_g('building %s in %s'), $sourcepackage, $debianfile);
+    my $tar = Dpkg::Source::Archive->new(filename => $debianfile);
+    $tar->create(options => \@tar_ignore, chdir => $dir);
+    $tar->add_directory('debian');
+    foreach my $binary ($binaryfiles->get_seen_binaries()) {
+        $tar->add_file($binary) unless $binary =~ m{^debian/};
+    }
+    $tar->finish();
+
+    $self->add_file($debianfile);
+}
+
+sub get_patch_header {
+    my ($self, $dir) = @_;
+    my $ph = File::Spec->catfile($dir, 'debian', 'source', 'local-patch-header');
+    unless (-f $ph) {
+        $ph = File::Spec->catfile($dir, 'debian', 'source', 'patch-header');
+    }
+    my $text;
+    if (-f $ph) {
+        open(my $ph_fh, '<', $ph) or syserr(_g('cannot read %s'), $ph);
+        $text = file_slurp($ph_fh);
+        close($ph_fh);
+        return $text;
+    }
+    my $ch_info = changelog_parse(offset => 0, count => 1,
+        file => File::Spec->catfile($dir, 'debian', 'changelog'));
+    return '' if not defined $ch_info;
+    my $header = Dpkg::Control->new(type => CTRL_UNKNOWN);
+    $header->{'Description'} = "<short summary of the patch>\n";
+    $header->{'Description'} .=
+"TODO: Put a short summary on the line above and replace this paragraph
+with a longer explanation of this change. Complete the meta-information
+with other relevant fields (see below for details). To make it easier, the
+information below has been extracted from the changelog. Adjust it or drop
+it.\n";
+    $header->{'Description'} .= $ch_info->{'Changes'} . "\n";
+    $header->{'Author'} = $ch_info->{'Maintainer'};
+    $text = "$header";
+    run_vendor_hook('extend-patch-header', \$text, $ch_info);
+    $text .= "\n---
+The information above should follow the Patch Tagging Guidelines, please
+checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
+are templates for supplementary fields that you might want to add:
+
+Origin: <vendor|upstream|other>, <url of original patch>
+Bug: <url in upstream bugtracker>
+Bug-Debian: http://bugs.debian.org/<bugnumber>
+Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
+Forwarded: <no|not-needed|url proving that it has been forwarded>
+Reviewed-By: <name and email of someone who approved the patch>
+Last-Update: <YYYY-MM-DD>\n\n";
+    return $text;
+}
+
+sub register_patch {
+    my ($self, $dir, $patch_file, $patch_name) = @_;
+    my $patch = File::Spec->catfile($dir, 'debian', 'patches', $patch_name);
+    if (-s $patch_file) {
+        copy($patch_file, $patch)
+            or syserr(_g('failed to copy %s to %s'), $patch_file, $patch);
+        chmod(0666 & ~ umask(), $patch)
+            or syserr(_g("unable to change permission of `%s'"), $patch);
+        my $applied = File::Spec->catfile($dir, 'debian', 'patches', '.dpkg-source-applied');
+        open(my $applied_fh, '>>', $applied)
+            or syserr(_g('cannot write %s'), $applied);
+        print { $applied_fh } "$patch\n";
+        close($applied_fh) or syserr(_g('cannot close %s'), $applied);
+    } elsif (-e $patch) {
+        unlink($patch) or syserr(_g('cannot remove %s'), $patch);
+    }
+    return $patch;
+}
+
+sub _is_bad_patch_name {
+    my ($dir, $patch_name) = @_;
+
+    return 1 if not defined($patch_name);
+    return 1 if not length($patch_name);
+
+    my $patch = File::Spec->catfile($dir, 'debian', 'patches', $patch_name);
+    if (-e $patch) {
+        warning(_g('cannot register changes in %s, this patch already exists'),
+                $patch);
+        return 1;
+    }
+    return 0;
+}
+
+sub do_commit {
+    my ($self, $dir) = @_;
+    my ($patch_name, $tmpdiff) = @{$self->{options}{ARGV}};
+
+    $self->prepare_build($dir);
+
+    # Try to fix up a broken relative filename for the patch
+    if ($tmpdiff and not -e $tmpdiff) {
+        $tmpdiff = File::Spec->catfile($dir, $tmpdiff)
+            unless File::Spec->file_name_is_absolute($tmpdiff);
+        error(_g("patch file '%s' doesn't exist"), $tmpdiff) if not -e $tmpdiff;
+    }
+
+    my $binaryfiles = Dpkg::Source::Package::V2::BinaryFiles->new($dir);
+    my $handle_binary = sub {
+        my ($self, $old, $new) = @_;
+        my $fn = File::Spec->abs2rel($new, $dir);
+        $binaryfiles->new_binary_found($fn);
+    };
+
+    unless ($tmpdiff) {
+        $tmpdiff = $self->generate_patch($dir, handle_binary => $handle_binary,
+                                         usage => 'commit');
+        $binaryfiles->update_debian_source_include_binaries();
+    }
+    push_exit_handler(sub { unlink($tmpdiff) });
+    unless (-s $tmpdiff) {
+        unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff);
+        info(_g('there are no local changes to record'));
+        return;
+    }
+    while (_is_bad_patch_name($dir, $patch_name)) {
+        # Ask the patch name interactively
+        print _g('Enter the desired patch name: ');
+        chomp($patch_name = <STDIN>);
+        $patch_name =~ s/\s+/-/g;
+        $patch_name =~ s/\///g;
+    }
+    mkpath(File::Spec->catdir($dir, 'debian', 'patches'));
+    my $patch = $self->register_patch($dir, $tmpdiff, $patch_name);
+    system('sensible-editor', $patch);
+    subprocerr('sensible-editor') if $?;
+    unlink($tmpdiff) or syserr(_g('cannot remove %s'), $tmpdiff);
+    pop_exit_handler();
+    info(_g('local changes have been recorded in a new patch: %s'), $patch);
+}
+
+package Dpkg::Source::Package::V2::BinaryFiles;
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use File::Path;
+
+sub new {
+    my ($this, $dir) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+        dir => $dir,
+        allowed_binaries => {},
+        seen_binaries => {},
+        include_binaries_path =>
+            File::Spec->catfile($dir, 'debian', 'source', 'include-binaries'),
+    };
+    bless $self, $class;
+    $self->load_allowed_binaries();
+    return $self;
+}
+
+sub new_binary_found {
+    my ($self, $path) = @_;
+
+    $self->{seen_binaries}{$path} = 1;
+}
+
+sub load_allowed_binaries {
+    my ($self) = @_;
+    my $incbin_file = $self->{include_binaries_path};
+    if (-f $incbin_file) {
+        open(my $incbin_fh, '<', $incbin_file)
+            or syserr(_g('cannot read %s'), $incbin_file);
+        while (defined($_ = <$incbin_fh>)) {
+            chomp; s/^\s*//; s/\s*$//;
+            next if /^#/ or /^$/;
+            $self->{allowed_binaries}{$_} = 1;
+        }
+        close($incbin_fh);
+    }
+}
+
+sub binary_is_allowed {
+    my ($self, $path) = @_;
+    return 1 if exists $self->{allowed_binaries}{$path};
+    return 0;
+}
+
+sub update_debian_source_include_binaries {
+    my ($self) = @_;
+
+    my @unknown_binaries = $self->get_unknown_binaries();
+    return unless scalar(@unknown_binaries);
+
+    my $incbin_file = $self->{include_binaries_path};
+    mkpath(File::Spec->catdir($self->{dir}, 'debian', 'source'));
+    open(my $incbin_fh, '>>', $incbin_file)
+        or syserr(_g('cannot write %s'), $incbin_file);
+    foreach my $binary (@unknown_binaries) {
+        print { $incbin_fh } "$binary\n";
+        info(_g('adding %s to %s'), $binary, 'debian/source/include-binaries');
+        $self->{allowed_binaries}{$binary} = 1;
+    }
+    close($incbin_fh);
+}
+
+sub get_unknown_binaries {
+    my ($self) = @_;
+    return grep { not $self->binary_is_allowed($_) } $self->get_seen_binaries();
+}
+
+sub get_seen_binaries {
+    my ($self) = @_;
+    my @seen = sort keys %{$self->{seen_binaries}};
+    return @seen;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Bzr.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Bzr.pm
new file mode 100644
index 0000000..1bf4eb9
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Bzr.pm
@@ -0,0 +1,210 @@
+#
+# bzr support for dpkg-source
+#
+# Copyright © 2007 Colin Watson <cjwatson@debian.org>.
+# Based on Dpkg::Source::Package::V3_0::git, which is:
+# Copyright © 2007 Joey Hess <joeyh@debian.org>.
+# Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V3::Bzr;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Source::Package);
+
+use Cwd;
+use File::Basename;
+use File::Find;
+use File::Temp qw(tempdir);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::Compression;
+use Dpkg::ErrorHandling;
+use Dpkg::Source::Archive;
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
+use Dpkg::Source::Functions qw(erasedir);
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub import {
+    foreach my $dir (split(/:/, $ENV{PATH})) {
+        if (-x "$dir/bzr") {
+            return 1;
+        }
+    }
+    error(_g('cannot unpack bzr-format source package because ' .
+             'bzr is not in the PATH'));
+}
+
+sub sanity_check {
+    my $srcdir = shift;
+
+    if (! -d "$srcdir/.bzr") {
+        error(_g('source directory is not the top directory of a bzr repository (%s/.bzr not present), but Format bzr was specified'),
+              $srcdir);
+    }
+
+    # Symlinks from .bzr to outside could cause unpack failures, or
+    # point to files they shouldn't, so check for and don't allow.
+    if (-l "$srcdir/.bzr") {
+        error(_g('%s is a symlink'), "$srcdir/.bzr");
+    }
+    my $abs_srcdir = Cwd::abs_path($srcdir);
+    find(sub {
+        if (-l $_) {
+            if (Cwd::abs_path(readlink($_)) !~ /^\Q$abs_srcdir\E(\/|$)/) {
+                error(_g('%s is a symlink to outside %s'),
+                      $File::Find::name, $srcdir);
+            }
+        }
+    }, "$srcdir/.bzr");
+
+    return 1;
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+
+    return (0, _g("doesn't contain a bzr repository")) unless -d "$dir/.bzr";
+    return 1;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    my @argv = @{$self->{options}{ARGV}};
+    # TODO: warn here?
+    #my @tar_ignore = map { "--exclude=$_" } @{$self->{options}{tar_ignore}};
+    my $diff_ignore_regex = $self->{options}{diff_ignore_regex};
+
+    $dir =~ s{/+$}{}; # Strip trailing /
+    my ($dirname, $updir) = fileparse($dir);
+
+    if (scalar(@argv)) {
+        usageerr(_g("-b takes only one parameter with format `%s'"),
+                 $self->{fields}{'Format'});
+    }
+
+    my $sourcepackage = $self->{fields}{'Source'};
+    my $basenamerev = $self->get_basename(1);
+    my $basename = $self->get_basename();
+    my $basedirname = $basename;
+    $basedirname =~ s/_/-/;
+
+    sanity_check($dir);
+
+    my $old_cwd = getcwd();
+    chdir($dir) or syserr(_g("unable to chdir to `%s'"), $dir);
+
+    # Check for uncommitted files.
+    # To support dpkg-source -i, remove any ignored files from the
+    # output of bzr status.
+    open(my $bzr_status_fh, '-|', 'bzr', 'status')
+        or subprocerr('bzr status');
+    my @files;
+    while (<$bzr_status_fh>) {
+        chomp;
+        next unless s/^ +//;
+        if (! length $diff_ignore_regex ||
+            ! m/$diff_ignore_regex/o) {
+            push @files, $_;
+        }
+    }
+    close($bzr_status_fh) or syserr(_g('bzr status exited nonzero'));
+    if (@files) {
+        error(_g('uncommitted, not-ignored changes in working directory: %s'),
+              join(' ', @files));
+    }
+
+    chdir($old_cwd) or syserr(_g("unable to chdir to `%s'"), $old_cwd);
+
+    my $tmp = tempdir("$dirname.bzr.XXXXXX", DIR => $updir);
+    push_exit_handler(sub { erasedir($tmp) });
+    my $tardir = "$tmp/$dirname";
+
+    system('bzr', 'branch', $dir, $tardir);
+    subprocerr("bzr branch $dir $tardir") if $?;
+
+    # Remove the working tree.
+    system('bzr', 'remove-tree', $tardir);
+    subprocerr("bzr remove-tree $tardir") if $?;
+
+    # Some branch metadata files are unhelpful.
+    unlink("$tardir/.bzr/branch/branch-name",
+           "$tardir/.bzr/branch/parent");
+
+    # Create the tar file
+    my $debianfile = "$basenamerev.bzr.tar." . $self->{options}{comp_ext};
+    info(_g('building %s in %s'),
+         $sourcepackage, $debianfile);
+    my $tar = Dpkg::Source::Archive->new(filename => $debianfile,
+                                         compression => $self->{options}{compression},
+                                         compression_level => $self->{options}{comp_level});
+    $tar->create(chdir => $tmp);
+    $tar->add_directory($dirname);
+    $tar->finish();
+
+    erasedir($tmp);
+    pop_exit_handler();
+
+    $self->add_file($debianfile);
+}
+
+# Called after a tarball is unpacked, to check out the working copy.
+sub do_extract {
+    my ($self, $newdirectory) = @_;
+    my $fields = $self->{fields};
+
+    my $dscdir = $self->{basedir};
+
+    my $basename = $self->get_basename();
+    my $basenamerev = $self->get_basename(1);
+
+    my @files = $self->get_files();
+    if (@files > 1) {
+        error(_g('format v3.0 uses only one source file'));
+    }
+    my $tarfile = $files[0];
+    my $comp_ext_regex = compression_get_file_extension_regex();
+    if ($tarfile !~ /^\Q$basenamerev\E\.bzr\.tar\.$comp_ext_regex$/) {
+        error(_g('expected %s, got %s'),
+              "$basenamerev.bzr.tar.$comp_ext_regex", $tarfile);
+    }
+
+    erasedir($newdirectory);
+
+    # Extract main tarball
+    info(_g('unpacking %s'), $tarfile);
+    my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
+    $tar->extract($newdirectory);
+
+    sanity_check($newdirectory);
+
+    my $old_cwd = getcwd();
+    chdir($newdirectory)
+        or syserr(_g("unable to chdir to `%s'"), $newdirectory);
+
+    # Reconstitute the working tree.
+    system('bzr', 'checkout');
+    subprocerr('bzr checkout') if $?;
+
+    chdir($old_cwd) or syserr(_g("unable to chdir to `%s'"), $old_cwd);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Custom.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Custom.pm
new file mode 100644
index 0000000..3ea9d05f
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Custom.pm
@@ -0,0 +1,63 @@
+# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V3::Custom;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Source::Package);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub parse_cmdline_option {
+    my ($self, $opt) = @_;
+    if ($opt =~ /^--target-format=(.*)$/) {
+        $self->{options}{target_format} = $1;
+        return 1;
+    }
+    return 0;
+}
+sub do_extract {
+    error(_g("Format `3.0 (custom)' is only used to create source packages"));
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+
+    return (0, _g('no files indicated on command line'))
+        unless scalar(@{$self->{options}{ARGV}});
+    return 1;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    # Update real target format
+    my $format = $self->{options}{target_format};
+    error(_g('--target-format option is missing')) unless $format;
+    $self->{fields}{'Format'} = $format;
+    # Add all files
+    foreach my $file (@{$self->{options}{ARGV}}) {
+        $self->add_file($file);
+    }
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Git.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Git.pm
new file mode 100644
index 0000000..21eb667
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Git.pm
@@ -0,0 +1,235 @@
+#
+# git support for dpkg-source
+#
+# Copyright © 2007,2010 Joey Hess <joeyh@debian.org>.
+# Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V3::Git;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.02';
+
+use parent qw(Dpkg::Source::Package);
+
+use Cwd qw(abs_path getcwd);
+use File::Basename;
+use File::Temp qw(tempdir);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
+use Dpkg::Source::Functions qw(erasedir);
+
+our $CURRENT_MINOR_VERSION = '0';
+
+# Remove variables from the environment that might cause git to do
+# something unexpected.
+delete $ENV{GIT_DIR};
+delete $ENV{GIT_INDEX_FILE};
+delete $ENV{GIT_OBJECT_DIRECTORY};
+delete $ENV{GIT_ALTERNATE_OBJECT_DIRECTORIES};
+delete $ENV{GIT_WORK_TREE};
+
+sub import {
+    foreach my $dir (split(/:/, $ENV{PATH})) {
+        if (-x "$dir/git") {
+            return 1;
+        }
+    }
+    error(_g('cannot unpack git-format source package because ' .
+             'git is not in the PATH'));
+}
+
+sub sanity_check {
+    my $srcdir = shift;
+
+    if (! -d "$srcdir/.git") {
+        error(_g('source directory is not the top directory of a git ' .
+                 'repository (%s/.git not present), but Format git was ' .
+                 'specified'), $srcdir);
+    }
+    if (-s "$srcdir/.gitmodules") {
+        error(_g('git repository %s uses submodules; this is not yet supported'),
+              $srcdir);
+    }
+
+    return 1;
+}
+
+sub parse_cmdline_option {
+    my ($self, $opt) = @_;
+    return 1 if $self->SUPER::parse_cmdline_option($opt);
+    if ($opt =~ /^--git-ref=(.*)$/) {
+        push @{$self->{options}{git_ref}}, $1;
+        return 1;
+    } elsif ($opt =~ /^--git-depth=(\d+)$/) {
+        $self->{options}{git_depth} = $1;
+        return 1;
+    }
+    return 0;
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+
+    return (0, _g("doesn't contain a git repository")) unless -d "$dir/.git";
+    return 1;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    my $diff_ignore_regex = $self->{options}{diff_ignore_regex};
+
+    $dir =~ s{/+$}{}; # Strip trailing /
+    my ($dirname, $updir) = fileparse($dir);
+    my $basenamerev = $self->get_basename(1);
+
+    sanity_check($dir);
+
+    my $old_cwd = getcwd();
+    chdir($dir) or syserr(_g("unable to chdir to `%s'"), $dir);
+
+    # Check for uncommitted files.
+    # To support dpkg-source -i, get a list of files
+    # equivalent to the ones git status finds, and remove any
+    # ignored files from it.
+    my @ignores = '--exclude-per-directory=.gitignore';
+    my $core_excludesfile = `git config --get core.excludesfile`;
+    chomp $core_excludesfile;
+    if (length $core_excludesfile && -e $core_excludesfile) {
+        push @ignores, "--exclude-from=$core_excludesfile";
+    }
+    if (-e '.git/info/exclude') {
+        push @ignores, '--exclude-from=.git/info/exclude';
+    }
+    open(my $git_ls_files_fh, '-|', 'git', 'ls-files', '--modified', '--deleted',
+         '-z', '--others', @ignores) or subprocerr('git ls-files');
+    my @files;
+    { local $/ = "\0";
+      while (<$git_ls_files_fh>) {
+          chomp;
+          if (! length $diff_ignore_regex ||
+              ! m/$diff_ignore_regex/o) {
+              push @files, $_;
+          }
+      }
+    }
+    close($git_ls_files_fh) or syserr(_g('git ls-files exited nonzero'));
+    if (@files) {
+        error(_g('uncommitted, not-ignored changes in working directory: %s'),
+              join(' ', @files));
+    }
+
+    # If a depth was specified, need to create a shallow clone and
+    # bundle that.
+    my $tmp;
+    my $shallowfile;
+    if ($self->{options}{git_depth}) {
+        chdir($old_cwd) or syserr(_g("unable to chdir to `%s'"), $old_cwd);
+        $tmp = tempdir("$dirname.git.XXXXXX", DIR => $updir);
+        push_exit_handler(sub { erasedir($tmp) });
+        my $clone_dir = "$tmp/repo.git";
+        # file:// is needed to avoid local cloning, which does not
+        # create a shallow clone.
+        info(_g('creating shallow clone with depth %s'),
+                $self->{options}{git_depth});
+        system('git', 'clone', '--depth=' . $self->{options}{git_depth},
+               '--quiet', '--bare', 'file://' . abs_path($dir), $clone_dir);
+        subprocerr('git clone') if $?;
+        chdir($clone_dir)
+            or syserr(_g("unable to chdir to `%s'"), $clone_dir);
+        $shallowfile = "$basenamerev.gitshallow";
+        system('cp', '-f', 'shallow', "$old_cwd/$shallowfile");
+        subprocerr('cp shallow') if $?;
+    }
+
+    # Create the git bundle.
+    my $bundlefile = "$basenamerev.git";
+    my @bundle_arg=$self->{options}{git_ref} ?
+        (@{$self->{options}{git_ref}}) : '--all';
+    info(_g('bundling: %s'), join(' ', @bundle_arg));
+    system('git', 'bundle', 'create', "$old_cwd/$bundlefile",
+           @bundle_arg,
+           'HEAD', # ensure HEAD is included no matter what
+           '--', # avoids ambiguity error when referring to eg, a debian branch
+    );
+    subprocerr('git bundle') if $?;
+
+    chdir($old_cwd) or syserr(_g("unable to chdir to `%s'"), $old_cwd);
+
+    if (defined $tmp) {
+        erasedir($tmp);
+        pop_exit_handler();
+    }
+
+    $self->add_file($bundlefile);
+    if (defined $shallowfile) {
+        $self->add_file($shallowfile);
+    }
+}
+
+sub do_extract {
+    my ($self, $newdirectory) = @_;
+    my $fields = $self->{fields};
+
+    my $dscdir = $self->{basedir};
+    my $basenamerev = $self->get_basename(1);
+
+    my @files = $self->get_files();
+    my ($bundle, $shallow);
+    foreach my $file (@files) {
+        if ($file =~ /^\Q$basenamerev\E\.git$/) {
+            if (! defined $bundle) {
+                $bundle = $file;
+            } else {
+                error(_g('format v3.0 (git) uses only one .git file'));
+            }
+        } elsif ($file =~ /^\Q$basenamerev\E\.gitshallow$/) {
+            if (! defined $shallow) {
+                $shallow = $file;
+            } else {
+                error(_g('format v3.0 (git) uses only one .gitshallow file'));
+            }
+        } else {
+            error(_g('format v3.0 (git) unknown file: %s', $file));
+        }
+    }
+    if (! defined $bundle) {
+        error(_g('format v3.0 (git) expected %s'), "$basenamerev.git");
+    }
+
+    erasedir($newdirectory);
+
+    # Extract git bundle.
+    info(_g('cloning %s'), $bundle);
+    system('git', 'clone', '--quiet', $dscdir . $bundle, $newdirectory);
+    subprocerr('git bundle') if $?;
+
+    if (defined $shallow) {
+        # Move shallow info file into place, so git does not
+        # try to follow parents of shallow refs.
+        info(_g('setting up shallow clone'));
+        system('cp', '-f',  $dscdir . $shallow, "$newdirectory/.git/shallow");
+        subprocerr('cp') if $?;
+    }
+
+    sanity_check($newdirectory);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Native.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Native.pm
new file mode 100644
index 0000000..a50f2a7
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Native.pm
@@ -0,0 +1,115 @@
+# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V3::Native;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Source::Package);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Compression;
+use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
+use Dpkg::Version;
+use Dpkg::Source::Archive;
+use Dpkg::Source::Functions qw(erasedir);
+
+use Cwd;
+use File::Basename;
+use File::Temp qw(tempfile);
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub do_extract {
+    my ($self, $newdirectory) = @_;
+    my $sourcestyle = $self->{options}{sourcestyle};
+    my $fields = $self->{fields};
+
+    my $dscdir = $self->{basedir};
+    my $basename = $self->get_basename();
+    my $basenamerev = $self->get_basename(1);
+
+    my $tarfile;
+    my $comp_ext_regex = compression_get_file_extension_regex();
+    foreach my $file ($self->get_files()) {
+	if ($file =~ /^\Q$basenamerev\E\.tar\.$comp_ext_regex$/) {
+            error(_g('multiple tarfiles in v1.0 source package')) if $tarfile;
+            $tarfile = $file;
+	} else {
+	    error(_g('unrecognized file for a native source package: %s'), $file);
+	}
+    }
+
+    error(_g('no tarfile in Files field')) unless $tarfile;
+
+    erasedir($newdirectory);
+    info(_g('unpacking %s'), $tarfile);
+    my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
+    $tar->extract($newdirectory);
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+
+    my $v = Dpkg::Version->new($self->{fields}->{'Version'});
+    warning (_g('native package version may not have a revision'))
+        unless $v->is_native();
+
+    return 1;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+    my @tar_ignore = map { "--exclude=$_" } @{$self->{options}{tar_ignore}};
+    my @argv = @{$self->{options}{ARGV}};
+
+    if (scalar(@argv)) {
+        usageerr(_g("-b takes only one parameter with format `%s'"),
+                 $self->{fields}{'Format'});
+    }
+
+    my $sourcepackage = $self->{fields}{'Source'};
+    my $basenamerev = $self->get_basename(1);
+    my $tarname = "$basenamerev.tar." . $self->{options}{comp_ext};
+
+    info(_g('building %s in %s'), $sourcepackage, $tarname);
+
+    my ($ntfh, $newtar) = tempfile("$tarname.new.XXXXXX",
+                                   DIR => getcwd(), UNLINK => 0);
+    push_exit_handler(sub { unlink($newtar) });
+
+    my ($dirname, $dirbase) = fileparse($dir);
+    my $tar = Dpkg::Source::Archive->new(filename => $newtar,
+                compression => compression_guess_from_filename($tarname),
+                compression_level => $self->{options}{comp_level});
+    $tar->create(options => \@tar_ignore, chdir => $dirbase);
+    $tar->add_directory($dirname);
+    $tar->finish();
+    rename($newtar, $tarname)
+        or syserr(_g("unable to rename `%s' (newly created) to `%s'"),
+                  $newtar, $tarname);
+    pop_exit_handler();
+    chmod(0666 &~ umask(), $tarname)
+        or syserr(_g("unable to change permission of `%s'"), $tarname);
+
+    $self->add_file($tarname);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Quilt.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Quilt.pm
new file mode 100644
index 0000000..6feb3f67
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Package/V3/Quilt.pm
@@ -0,0 +1,291 @@
+# Copyright © 2008-2012 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Package::V3::Quilt;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+# Based on wig&pen implementation
+use parent qw(Dpkg::Source::Package::V2);
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Util qw(:list);
+use Dpkg::Version;
+use Dpkg::Source::Patch;
+use Dpkg::Source::Functions qw(erasedir fs_time);
+use Dpkg::Source::Quilt;
+use Dpkg::Exit;
+
+use File::Spec;
+use File::Copy;
+
+our $CURRENT_MINOR_VERSION = '0';
+
+sub init_options {
+    my ($self) = @_;
+    $self->{options}{single_debian_patch} = 0
+        unless exists $self->{options}{single_debian_patch};
+    $self->{options}{allow_version_of_quilt_db} = []
+        unless exists $self->{options}{allow_version_of_quilt_db};
+
+    $self->SUPER::init_options();
+}
+
+sub parse_cmdline_option {
+    my ($self, $opt) = @_;
+    return 1 if $self->SUPER::parse_cmdline_option($opt);
+    if ($opt =~ /^--single-debian-patch$/) {
+        $self->{options}{single_debian_patch} = 1;
+        # For backwards compatibility.
+        $self->{options}{auto_commit} = 1;
+        return 1;
+    } elsif ($opt =~ /^--allow-version-of-quilt-db=(.*)$/) {
+        push @{$self->{options}{allow_version_of_quilt_db}}, $1;
+        return 1;
+    }
+    return 0;
+}
+
+sub build_quilt_object {
+    my ($self, $dir) = @_;
+    return $self->{quilt}{$dir} if exists $self->{quilt}{$dir};
+    $self->{quilt}{$dir} = Dpkg::Source::Quilt->new($dir);
+    return $self->{quilt}{$dir};
+}
+
+sub can_build {
+    my ($self, $dir) = @_;
+    my ($code, $msg) = $self->SUPER::can_build($dir);
+    return ($code, $msg) if $code == 0;
+
+    my $v = Dpkg::Version->new($self->{fields}->{'Version'});
+    warning (_g('version does not contain a revision')) if $v->is_native();
+
+    my $quilt = $self->build_quilt_object($dir);
+    $msg = $quilt->find_problems();
+    return (0, $msg) if $msg;
+    return 1;
+}
+
+sub get_autopatch_name {
+    my ($self) = @_;
+    if ($self->{options}{single_debian_patch}) {
+        return 'debian-changes';
+    } else {
+        return 'debian-changes-' . $self->{fields}{'Version'};
+    }
+}
+
+sub apply_patches {
+    my ($self, $dir, %opts) = @_;
+
+    if ($opts{usage} eq 'unpack') {
+        $opts{verbose} = 1;
+    } elsif ($opts{usage} eq 'build') {
+        $opts{warn_options} = 1;
+        $opts{verbose} = 0;
+    }
+
+    my $quilt = $self->build_quilt_object($dir);
+    $quilt->load_series(%opts) if $opts{warn_options}; # Trigger warnings
+
+    # Always create the quilt db so that if the maintainer calls quilt to
+    # create a patch, it's stored in the right directory
+    $quilt->write_db();
+
+    # Update debian/patches/series symlink if needed to allow quilt usage
+    my $series = $quilt->get_series_file();
+    my $basename = (File::Spec->splitpath($series))[2];
+    if ($basename ne 'series') {
+        my $dest = $quilt->get_patch_file('series');
+        unlink($dest) if -l $dest;
+        unless (-f _) { # Don't overwrite real files
+            symlink($basename, $dest)
+                or syserr(_g("can't create symlink %s"), $dest);
+        }
+    }
+
+    return unless scalar($quilt->series());
+
+    if ($opts{usage} eq 'preparation' and
+        $self->{options}{unapply_patches} eq 'auto') {
+        # We're applying the patches in --before-build, remember to unapply
+        # them afterwards in --after-build
+        my $pc_unapply = $quilt->get_db_file('.dpkg-source-unapply');
+        open(my $unapply_fh, '>', $pc_unapply)
+            or syserr(_g('cannot write %s'), $pc_unapply);
+        close($unapply_fh);
+    }
+
+    # Apply patches
+    my $pc_applied = $quilt->get_db_file('applied-patches');
+    $opts{timestamp} = fs_time($pc_applied);
+    if ($opts{skip_auto}) {
+        my $auto_patch = $self->get_autopatch_name();
+        $quilt->push(%opts) while ($quilt->next() and $quilt->next() ne $auto_patch);
+    } else {
+        $quilt->push(%opts) while $quilt->next();
+    }
+}
+
+sub unapply_patches {
+    my ($self, $dir, %opts) = @_;
+
+    my $quilt = $self->build_quilt_object($dir);
+
+    $opts{verbose} //= 1;
+
+    my $pc_applied = $quilt->get_db_file('applied-patches');
+    my @applied = $quilt->applied();
+    $opts{timestamp} = fs_time($pc_applied) if @applied;
+
+    $quilt->pop(%opts) while $quilt->top();
+
+    erasedir($quilt->get_db_dir());
+}
+
+sub prepare_build {
+    my ($self, $dir) = @_;
+    $self->SUPER::prepare_build($dir);
+    # Skip .pc directories of quilt by default and ignore difference
+    # on debian/patches/series symlinks and d/p/.dpkg-source-applied
+    # stamp file created by ourselves
+    my $func = sub {
+        return 1 if $_[0] =~ m{^debian/patches/series$} and -l $_[0];
+        return 1 if $_[0] =~ /^\.pc(\/|$)/;
+        return 1 if $_[0] =~ /$self->{options}{diff_ignore_regex}/;
+        return 0;
+    };
+    $self->{diff_options}{diff_ignore_func} = $func;
+}
+
+sub do_build {
+    my ($self, $dir) = @_;
+
+    my $quilt = $self->build_quilt_object($dir);
+    my $version = $quilt->get_db_version();
+
+    if (defined($version) and $version != 2) {
+        if (any { $version eq $_ }
+            @{$self->{options}{allow_version_of_quilt_db}})
+        {
+            warning(_g('unsupported version of the quilt metadata: %s'), $version);
+        } else {
+            error(_g('unsupported version of the quilt metadata: %s'), $version);
+        }
+    }
+
+    $self->SUPER::do_build($dir);
+}
+
+sub after_build {
+    my ($self, $dir) = @_;
+    my $quilt = $self->build_quilt_object($dir);
+    my $pc_unapply = $quilt->get_db_file('.dpkg-source-unapply');
+    my $opt_unapply = $self->{options}{unapply_patches};
+    if (($opt_unapply eq 'auto' and -e $pc_unapply) or $opt_unapply eq 'yes') {
+        unlink($pc_unapply);
+        $self->unapply_patches($dir);
+    }
+}
+
+sub check_patches_applied {
+    my ($self, $dir) = @_;
+
+    my $quilt = $self->build_quilt_object($dir);
+    my $next = $quilt->next();
+    return if not defined $next;
+
+    my $first_patch = File::Spec->catfile($dir, 'debian', 'patches', $next);
+    my $patch_obj = Dpkg::Source::Patch->new(filename => $first_patch);
+    return unless $patch_obj->check_apply($dir);
+
+    $self->apply_patches($dir, usage => 'preparation', verbose => 1);
+}
+
+sub _add_line {
+    my ($file, $line) = @_;
+
+    open(my $file_fh, '>>', $file) or syserr(_g('cannot write %s'), $file);
+    print { $file_fh } "$line\n";
+    close($file_fh);
+}
+
+sub _drop_line {
+    my ($file, $re) = @_;
+
+    open(my $file_fh, '<', $file) or syserr(_g('cannot read %s'), $file);
+    my @lines = <$file_fh>;
+    close($file_fh);
+    open($file_fh, '>', $file) or syserr(_g('cannot write %s'), $file);
+    print { $file_fh } $_ foreach grep { not /^\Q$re\E\s*$/ } @lines;
+    close($file_fh);
+}
+
+sub register_patch {
+    my ($self, $dir, $tmpdiff, $patch_name) = @_;
+
+    my $quilt = $self->build_quilt_object($dir);
+
+    my @patches = $quilt->series();
+    my $has_patch = (grep { $_ eq $patch_name } @patches) ? 1 : 0;
+    my $series = $quilt->get_series_file();
+    my $applied = $quilt->get_db_file('applied-patches');
+    my $patch = $quilt->get_patch_file($patch_name);
+
+    if (-s $tmpdiff) {
+        copy($tmpdiff, $patch)
+            or syserr(_g('failed to copy %s to %s'), $tmpdiff, $patch);
+        chmod(0666 & ~ umask(), $patch)
+            or syserr(_g("unable to change permission of `%s'"), $patch);
+    } elsif (-e $patch) {
+        unlink($patch) or syserr(_g('cannot remove %s'), $patch);
+    }
+
+    if (-e $patch) {
+        $quilt->setup_db();
+        # Add patch to series file
+        if (not $has_patch) {
+            _add_line($series, $patch_name);
+            _add_line($applied, $patch_name);
+            $quilt->load_series();
+            $quilt->load_db();
+        }
+        # Ensure quilt meta-data are created and in sync with some trickery:
+        # reverse-apply the patch, drop .pc/$patch, re-apply it
+        # with the correct options to recreate the backup files
+        $quilt->pop(reverse_apply => 1);
+        $quilt->push();
+    } else {
+        # Remove auto_patch from series
+        if ($has_patch) {
+            _drop_line($series, $patch_name);
+            _drop_line($applied, $patch_name);
+            erasedir($quilt->get_db_file($patch_name));
+            $quilt->load_db();
+            $quilt->load_series();
+        }
+        # Clean up empty series
+        unlink($series) if -z $series;
+    }
+    return $patch;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Patch.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Patch.pm
new file mode 100644
index 0000000..a3fb846
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Patch.pm
@@ -0,0 +1,640 @@
+# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Patch;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg;
+use Dpkg::Gettext;
+use Dpkg::IPC;
+use Dpkg::ErrorHandling;
+use Dpkg::Source::Functions qw(fs_time);
+
+use POSIX qw(:errno_h :sys_wait_h);
+use File::Find;
+use File::Basename;
+use File::Spec;
+use File::Path;
+use File::Compare;
+use Fcntl ':mode';
+#XXX: Needed for sub-second timestamps, require recent perl
+#use Time::HiRes qw(stat);
+
+use parent qw(Dpkg::Compression::FileHandle);
+
+sub create {
+    my ($self, %opts) = @_;
+    $self->ensure_open('w'); # Creates the file
+    *$self->{errors} = 0;
+    *$self->{empty} = 1;
+    if ($opts{old} and $opts{new}) {
+        $opts{old} = '/dev/null' unless -e $opts{old};
+        $opts{new} = '/dev/null' unless -e $opts{new};
+        if (-d $opts{old} and -d $opts{new}) {
+            $self->add_diff_directory($opts{old}, $opts{new}, %opts);
+        } elsif (-f $opts{old} and -f $opts{new}) {
+            $self->add_diff_file($opts{old}, $opts{new}, %opts);
+        } else {
+            $self->_fail_not_same_type($opts{old}, $opts{new});
+        }
+        $self->finish() unless $opts{nofinish};
+    }
+}
+
+sub set_header {
+    my ($self, $header) = @_;
+    *$self->{header} = $header;
+}
+
+sub add_diff_file {
+    my ($self, $old, $new, %opts) = @_;
+    $opts{include_timestamp} = 0 unless exists $opts{include_timestamp};
+    my $handle_binary = $opts{handle_binary_func} || sub {
+        my ($self, $old, $new) = @_;
+        $self->_fail_with_msg($new, _g('binary file contents changed'));
+    };
+    # Optimization to avoid forking diff if unnecessary
+    return 1 if compare($old, $new, 4096) == 0;
+    # Default diff options
+    my @options;
+    if ($opts{options}) {
+        push @options, @{$opts{options}};
+    } else {
+        push @options, '-p';
+    }
+    # Add labels
+    if ($opts{label_old} and $opts{label_new}) {
+	if ($opts{include_timestamp}) {
+	    my $ts = (stat($old))[9];
+	    my $t = POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime($ts));
+	    $opts{label_old} .= sprintf("\t%s.%09d +0000", $t,
+	                                ($ts - int($ts)) * 1_000_000_000);
+	    $ts = (stat($new))[9];
+	    $t = POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime($ts));
+	    $opts{label_new} .= sprintf("\t%s.%09d +0000", $t,
+	                                ($ts - int($ts)) * 1_000_000_000);
+	} else {
+	    # Space in filenames need special treatment
+	    $opts{label_old} .= "\t" if $opts{label_old} =~ / /;
+	    $opts{label_new} .= "\t" if $opts{label_new} =~ / /;
+	}
+        push @options, '-L', $opts{label_old},
+                       '-L', $opts{label_new};
+    }
+    # Generate diff
+    my $diffgen;
+    my $diff_pid = spawn(
+        exec => [ 'diff', '-u', @options, '--', $old, $new ],
+        env => { LC_ALL => 'C', LANG => 'C', TZ => 'UTC0' },
+        to_pipe => \$diffgen,
+    );
+    # Check diff and write it in patch file
+    my $difflinefound = 0;
+    my $binary = 0;
+    while (<$diffgen>) {
+        if (m/^(?:binary|[^-+\@ ].*\bdiffer\b)/i) {
+            $binary = 1;
+            &$handle_binary($self, $old, $new);
+            last;
+        } elsif (m/^[-+\@ ]/) {
+            $difflinefound++;
+        } elsif (m/^\\ /) {
+            warning(_g('file %s has no final newline (either ' .
+                       'original or modified version)'), $new);
+        } else {
+            chomp;
+            error(_g("unknown line from diff -u on %s: `%s'"), $new, $_);
+        }
+	if (*$self->{empty} and defined(*$self->{header})) {
+	    $self->print(*$self->{header}) or syserr(_g('failed to write'));
+	    *$self->{empty} = 0;
+	}
+        print { $self } $_ or syserr(_g('failed to write'));
+    }
+    close($diffgen) or syserr('close on diff pipe');
+    wait_child($diff_pid, nocheck => 1,
+               cmdline => "diff -u @options -- $old $new");
+    # Verify diff process ended successfully
+    # Exit code of diff: 0 => no difference, 1 => diff ok, 2 => error
+    # Ignore error if binary content detected
+    my $exit = WEXITSTATUS($?);
+    unless (WIFEXITED($?) && ($exit == 0 || $exit == 1 || $binary)) {
+        subprocerr(_g('diff on %s'), $new);
+    }
+    return ($exit == 0 || $exit == 1);
+}
+
+sub add_diff_directory {
+    my ($self, $old, $new, %opts) = @_;
+    # TODO: make this function more configurable
+    # - offer to disable some checks
+    my $basedir = $opts{basedirname} || basename($new);
+    my $inc_removal = $opts{include_removal} || 0;
+    my $diff_ignore;
+    if ($opts{diff_ignore_func}) {
+        $diff_ignore = $opts{diff_ignore_func};
+    } elsif ($opts{diff_ignore_regex}) {
+        $diff_ignore = sub { return $_[0] =~ /$opts{diff_ignore_regex}/o };
+    } else {
+        $diff_ignore = sub { return 0 };
+    }
+
+    my @diff_files;
+    my %files_in_new;
+    my $scan_new = sub {
+        my $fn = (length > length($new)) ? substr($_, length($new) + 1) : '.';
+        return if &$diff_ignore($fn);
+        $files_in_new{$fn} = 1;
+        lstat("$new/$fn") or syserr(_g('cannot stat file %s'), "$new/$fn");
+        my $mode = S_IMODE((lstat(_))[2]);
+        my $size = (lstat(_))[7];
+        if (-l _) {
+            unless (-l "$old/$fn") {
+                $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+                return;
+            }
+            my $n = readlink("$new/$fn");
+            unless (defined $n) {
+                syserr(_g('cannot read link %s'), "$new/$fn");
+            }
+            my $n2 = readlink("$old/$fn");
+            unless (defined $n2) {
+                syserr(_g('cannot read link %s'), "$old/$fn");
+            }
+            unless ($n eq $n2) {
+                $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+            }
+        } elsif (-f _) {
+            my $old_file = "$old/$fn";
+            if (not lstat("$old/$fn")) {
+                if ($! != ENOENT) {
+                    syserr(_g('cannot stat file %s'), "$old/$fn");
+                }
+                $old_file = '/dev/null';
+            } elsif (not -f _) {
+                $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+                return;
+            }
+
+            my $label_old = "$basedir.orig/$fn";
+            if ($opts{use_dev_null}) {
+                $label_old = $old_file if $old_file eq '/dev/null';
+            }
+            push @diff_files, [$fn, $mode, $size, $old_file, "$new/$fn",
+                               $label_old, "$basedir/$fn"];
+        } elsif (-p _) {
+            unless (-p "$old/$fn") {
+                $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+            }
+        } elsif (-b _ || -c _ || -S _) {
+            $self->_fail_with_msg("$new/$fn",
+                _g('device or socket is not allowed'));
+        } elsif (-d _) {
+            if (not lstat("$old/$fn")) {
+                if ($! != ENOENT) {
+                    syserr(_g('cannot stat file %s'), "$old/$fn");
+                }
+            } elsif (not -d _) {
+                $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+            }
+        } else {
+            $self->_fail_with_msg("$new/$fn", _g('unknown file type'));
+        }
+    };
+    my $scan_old = sub {
+        my $fn = (length > length($old)) ? substr($_, length($old) + 1) : '.';
+        return if &$diff_ignore($fn);
+        return if $files_in_new{$fn};
+        lstat("$old/$fn") or syserr(_g('cannot stat file %s'), "$old/$fn");
+        if (-f _) {
+            if ($inc_removal) {
+                push @diff_files, [$fn, 0, 0, "$old/$fn", '/dev/null',
+                                   "$basedir.orig/$fn", '/dev/null'];
+            } else {
+                warning(_g('ignoring deletion of file %s'), $fn);
+            }
+        } elsif (-d _) {
+            warning(_g('ignoring deletion of directory %s'), $fn);
+        } elsif (-l _) {
+            warning(_g('ignoring deletion of symlink %s'), $fn);
+        } else {
+            $self->_fail_not_same_type("$old/$fn", "$new/$fn");
+        }
+    };
+
+    find({ wanted => $scan_new, no_chdir => 1 }, $new);
+    find({ wanted => $scan_old, no_chdir => 1 }, $old);
+
+    if ($opts{order_from} and -e $opts{order_from}) {
+        my $order_from = Dpkg::Source::Patch->new(
+            filename => $opts{order_from});
+        my $analysis = $order_from->analyze($basedir, verbose => 0);
+        my %patchorder;
+        my $i = 0;
+        foreach my $fn (@{$analysis->{patchorder}}) {
+            $fn =~ s{^[^/]+/}{};
+            $patchorder{$fn} = $i++;
+        }
+        # 'quilt refresh' sorts files as follows:
+        #   - Any files in the existing patch come first, in the order in
+        #     which they appear in the existing patch.
+        #   - New files follow, sorted lexicographically.
+        # This seems a reasonable policy to follow, and avoids autopatches
+        # being shuffled when they are regenerated.
+        foreach my $diff_file (sort { $a->[0] cmp $b->[0] } @diff_files) {
+            my $fn = $diff_file->[0];
+            $patchorder{$fn} = $i++ unless exists $patchorder{$fn};
+        }
+        @diff_files = sort { $patchorder{$a->[0]} <=> $patchorder{$b->[0]} }
+                      @diff_files;
+    } else {
+        @diff_files = sort { $a->[0] cmp $b->[0] } @diff_files;
+    }
+
+    foreach my $diff_file (@diff_files) {
+        my ($fn, $mode, $size,
+            $old_file, $new_file, $label_old, $label_new) = @$diff_file;
+        my $success = $self->add_diff_file($old_file, $new_file,
+                                           label_old => $label_old,
+                                           label_new => $label_new, %opts);
+        if ($success and
+            $old_file eq '/dev/null' and $new_file ne '/dev/null') {
+            if (not $size) {
+                warning(_g("newly created empty file '%s' will not " .
+                           'be represented in diff'), $fn);
+            } else {
+                if ($mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+                    warning(_g("executable mode %04o of '%s' will " .
+                               'not be represented in diff'), $mode, $fn)
+                        unless $fn eq 'debian/rules';
+                }
+                if ($mode & (S_ISUID | S_ISGID | S_ISVTX)) {
+                    warning(_g("special mode %04o of '%s' will not " .
+                               'be represented in diff'), $mode, $fn);
+                }
+            }
+        }
+    }
+}
+
+sub finish {
+    my ($self) = @_;
+    close($self) or syserr(_g('cannot close %s'), $self->get_filename());
+    return not *$self->{errors};
+}
+
+sub register_error {
+    my ($self) = @_;
+    *$self->{errors}++;
+}
+sub _fail_with_msg {
+    my ($self, $file, $msg) = @_;
+    errormsg(_g('cannot represent change to %s: %s'), $file, $msg);
+    $self->register_error();
+}
+sub _fail_not_same_type {
+    my ($self, $old, $new) = @_;
+    my $old_type = get_type($old);
+    my $new_type = get_type($new);
+    errormsg(_g('cannot represent change to %s:'), $new);
+    errormsg(_g('  new version is %s'), $new_type);
+    errormsg(_g('  old version is %s'), $old_type);
+    $self->register_error();
+}
+
+sub _getline {
+    my $handle = shift;
+
+    my $line = <$handle>;
+    if (defined $line) {
+        # Strip end-of-line chars
+        chomp($line);
+        $line =~ s/\r$//;
+    }
+    return $line;
+}
+
+sub _intuit_file_patched {
+    my ($old, $new) = @_;
+
+    return $new unless defined $old;
+    return $old unless defined $new;
+    return $new if -e $new and not -e $old;
+    return $old if -e $old and not -e $new;
+
+    # We don't consider the case where both files are non-existent and
+    # where patch picks the one with the fewest directories to create
+    # since dpkg-source will pre-create the required directories
+
+    # Precalculate metrics used by patch
+    my ($tmp_o, $tmp_n) = ($old, $new);
+    my ($len_o, $len_n) = (length($old), length($new));
+    $tmp_o =~ s{[/\\]+}{/}g;
+    $tmp_n =~ s{[/\\]+}{/}g;
+    my $nb_comp_o = ($tmp_o =~ tr{/}{/});
+    my $nb_comp_n = ($tmp_n =~ tr{/}{/});
+    $tmp_o =~ s{^.*/}{};
+    $tmp_n =~ s{^.*/}{};
+    my ($blen_o, $blen_n) = (length($tmp_o), length($tmp_n));
+
+    # Decide like patch would
+    if ($nb_comp_o != $nb_comp_n) {
+        return ($nb_comp_o < $nb_comp_n) ? $old : $new;
+    } elsif ($blen_o != $blen_n) {
+        return ($blen_o < $blen_n) ? $old : $new;
+    } elsif ($len_o != $len_n) {
+        return ($len_o < $len_n) ? $old : $new;
+    }
+    return $old;
+}
+
+# Fetch the header filename ignoring the optional timestamp
+sub _fetch_filename {
+    my ($diff, $header) = @_;
+
+    # Strip any leading spaces.
+    $header =~ s/^\s+//;
+
+    # Is it a C-style string?
+    if ($header =~ m/^"/) {
+        error(_g('diff %s patches file with C-style encoded filename'), $diff);
+    } else {
+        # Tab is the official separator, it's always used when
+        # filename contain spaces. Try it first, otherwise strip on space
+        # if there's no tab
+        $header =~ s/\s.*// unless $header =~ s/\t.*//;
+    }
+
+    return $header;
+}
+
+# check diff for sanity, find directories to create as a side effect
+sub analyze {
+    my ($self, $destdir, %opts) = @_;
+
+    $opts{verbose} //= 1;
+    my $diff = $self->get_filename();
+    my %filepatched;
+    my %dirtocreate;
+    my @patchorder;
+    my $patch_header = '';
+    my $diff_count = 0;
+
+    $_ = _getline($self);
+
+  HUNK:
+    while (defined($_) or not eof($self)) {
+	my (%path, %fn);
+	# skip comments leading up to patch (if any)
+	while (1) {
+	    if (/^(?:--- |\+\+\+ |@@ -)/) {
+		last;
+	    } else {
+		$patch_header .= "$_\n";
+	    }
+	    last HUNK if not defined($_ = _getline($self));
+	}
+	$diff_count++;
+	# read file header (---/+++ pair)
+	unless(s/^--- //) {
+	    error(_g("expected ^--- in line %d of diff `%s'"), $., $diff);
+	}
+        $path{old} = $_ = _fetch_filename($diff, $_);
+	$fn{old} = $_ if $_ ne '/dev/null' and s{^[^/]*/+}{$destdir/};
+	if (/\.dpkg-orig$/) {
+	    error(_g("diff `%s' patches file with name ending .dpkg-orig"), $diff);
+	}
+
+	unless (defined($_ = _getline($self))) {
+	    error(_g("diff `%s' finishes in middle of ---/+++ (line %d)"), $diff, $.);
+	}
+	unless (s/^\+\+\+ //) {
+	    error(_g("line after --- isn't as expected in diff `%s' (line %d)"), $diff, $.);
+	}
+        $path{new} = $_ = _fetch_filename($diff, $_);
+	$fn{new} = $_ if $_ ne '/dev/null' and s{^[^/]*/+}{$destdir/};
+
+	unless (defined $fn{old} or defined $fn{new}) {
+	    error(_g("none of the filenames in ---/+++ are valid in diff '%s' (line %d)"),
+		  $diff, $.);
+	}
+
+	# Safety checks on both filenames that patch could use
+	foreach my $key ('old', 'new') {
+	    next unless defined $fn{$key};
+	    if ($path{$key} =~ m{/\.\./}) {
+		error(_g('%s contains an insecure path: %s'), $diff, $path{$key});
+	    }
+	    my $path = $fn{$key};
+	    while (1) {
+		if (-l $path) {
+		    error(_g('diff %s modifies file %s through a symlink: %s'),
+			  $diff, $fn{$key}, $path);
+		}
+		last unless $path =~ s{/+[^/]*$}{};
+		last if length($path) <= length($destdir); # $destdir is assumed safe
+	    }
+	}
+
+        if ($path{old} eq '/dev/null' and $path{new} eq '/dev/null') {
+            error(_g("original and modified files are /dev/null in diff `%s' (line %d)"),
+                  $diff, $.);
+        } elsif ($path{new} eq '/dev/null') {
+            error(_g("file removal without proper filename in diff `%s' (line %d)"),
+                  $diff, $. - 1) unless defined $fn{old};
+            if ($opts{verbose}) {
+                warning(_g('diff %s removes a non-existing file %s (line %d)'),
+                        $diff, $fn{old}, $.) unless -e $fn{old};
+            }
+        }
+	my $fn = _intuit_file_patched($fn{old}, $fn{new});
+
+	my $dirname = $fn;
+	if ($dirname =~ s{/[^/]+$}{} and not -d $dirname) {
+	    $dirtocreate{$dirname} = 1;
+	}
+
+	if (-e $fn and not -f _) {
+	    error(_g("diff `%s' patches something which is not a plain file"), $diff);
+	}
+
+	if ($filepatched{$fn}) {
+	    warning(_g("diff `%s' patches file %s twice"), $diff, $fn)
+		if $opts{verbose};
+	} else {
+	    $filepatched{$fn} = 1;
+	    push @patchorder, $fn;
+	}
+
+	# read hunks
+	my $hunk = 0;
+	while (defined($_ = _getline($self))) {
+	    # read hunk header (@@)
+	    next if /^\\ /;
+	    last unless (/^@@ -\d+(,(\d+))? \+\d+(,(\d+))? @\@( .*)?$/);
+	    my ($olines, $nlines) = ($1 ? $2 : 1, $3 ? $4 : 1);
+	    # read hunk
+	    while ($olines || $nlines) {
+		unless (defined($_ = _getline($self))) {
+                    if (($olines == $nlines) and ($olines < 3)) {
+                        warning(_g("unexpected end of diff `%s'"), $diff)
+                            if $opts{verbose};
+                        last;
+                    } else {
+                        error(_g("unexpected end of diff `%s'"), $diff);
+                    }
+		}
+		next if /^\\ /;
+		# Check stats
+		if    (/^ / || /^$/)  { --$olines; --$nlines; }
+		elsif (/^-/)  { --$olines; }
+		elsif (/^\+/) { --$nlines; }
+		else {
+		    error(_g("expected [ +-] at start of line %d of diff `%s'"),
+		          $., $diff);
+		}
+	    }
+	    $hunk++;
+	}
+	unless($hunk) {
+	    error(_g("expected ^\@\@ at line %d of diff `%s'"), $., $diff);
+	}
+    }
+    close($self);
+    unless ($diff_count) {
+	warning(_g("diff `%s' doesn't contain any patch"), $diff)
+	    if $opts{verbose};
+    }
+    *$self->{analysis}{$destdir}{dirtocreate} = \%dirtocreate;
+    *$self->{analysis}{$destdir}{filepatched} = \%filepatched;
+    *$self->{analysis}{$destdir}{patchorder} = \@patchorder;
+    *$self->{analysis}{$destdir}{patchheader} = $patch_header;
+    return *$self->{analysis}{$destdir};
+}
+
+sub prepare_apply {
+    my ($self, $analysis, %opts) = @_;
+    if ($opts{create_dirs}) {
+	foreach my $dir (keys %{$analysis->{dirtocreate}}) {
+	    eval { mkpath($dir, 0, 0777); };
+	    syserr(_g('cannot create directory %s'), $dir) if $@;
+	}
+    }
+}
+
+sub apply {
+    my ($self, $destdir, %opts) = @_;
+    # Set default values to options
+    $opts{force_timestamp} = 1 unless exists $opts{force_timestamp};
+    $opts{remove_backup} = 1 unless exists $opts{remove_backup};
+    $opts{create_dirs} = 1 unless exists $opts{create_dirs};
+    $opts{options} ||= [ '-t', '-F', '0', '-N', '-p1', '-u',
+            '-V', 'never', '-g0', '-b', '-z', '.dpkg-orig'];
+    $opts{add_options} ||= [];
+    push @{$opts{options}}, @{$opts{add_options}};
+    # Check the diff and create missing directories
+    my $analysis = $self->analyze($destdir, %opts);
+    $self->prepare_apply($analysis, %opts);
+    # Apply the patch
+    $self->ensure_open('r');
+    my ($stdout, $stderr) = ('', '');
+    spawn(
+	exec => [ 'patch', @{$opts{options}} ],
+	chdir => $destdir,
+	env => { LC_ALL => 'C', LANG => 'C' },
+	delete_env => [ 'POSIXLY_CORRECT' ], # ensure expected patch behaviour
+	wait_child => 1,
+	nocheck => 1,
+	from_handle => $self->get_filehandle(),
+	to_string => \$stdout,
+	error_to_string => \$stderr,
+    );
+    if ($?) {
+	print { *STDOUT } $stdout;
+	print { *STDERR } $stderr;
+	subprocerr('LC_ALL=C patch ' . join(' ', @{$opts{options}}) .
+	           ' < ' . $self->get_filename());
+    }
+    $self->close();
+    # Reset the timestamp of all the patched files
+    # and remove .dpkg-orig files
+    my @files = keys %{$analysis->{filepatched}};
+    my $now = $opts{timestamp};
+    $now ||= fs_time($files[0]) if $opts{force_timestamp} && scalar @files;
+    foreach my $fn (@files) {
+	if ($opts{force_timestamp}) {
+	    utime($now, $now, $fn) or $! == ENOENT
+		or syserr(_g('cannot change timestamp for %s'), $fn);
+	}
+	if ($opts{remove_backup}) {
+	    $fn .= '.dpkg-orig';
+	    unlink($fn) or syserr(_g('remove patch backup file %s'), $fn);
+	}
+    }
+    return $analysis;
+}
+
+# Verify if check will work...
+sub check_apply {
+    my ($self, $destdir, %opts) = @_;
+    # Set default values to options
+    $opts{create_dirs} = 1 unless exists $opts{create_dirs};
+    $opts{options} ||= [ '--dry-run', '-s', '-t', '-F', '0', '-N', '-p1', '-u',
+            '-V', 'never', '-g0', '-b', '-z', '.dpkg-orig'];
+    $opts{add_options} ||= [];
+    push @{$opts{options}}, @{$opts{add_options}};
+    # Check the diff and create missing directories
+    my $analysis = $self->analyze($destdir, %opts);
+    $self->prepare_apply($analysis, %opts);
+    # Apply the patch
+    $self->ensure_open('r');
+    my $patch_pid = spawn(
+	exec => [ 'patch', @{$opts{options}} ],
+	chdir => $destdir,
+	env => { LC_ALL => 'C', LANG => 'C' },
+	delete_env => [ 'POSIXLY_CORRECT' ], # ensure expected patch behaviour
+	from_handle => $self->get_filehandle(),
+	to_file => '/dev/null',
+	error_to_file => '/dev/null',
+    );
+    wait_child($patch_pid, nocheck => 1);
+    my $exit = WEXITSTATUS($?);
+    subprocerr('patch --dry-run') unless WIFEXITED($?);
+    $self->close();
+    return ($exit == 0);
+}
+
+# Helper functions
+sub get_type {
+    my $file = shift;
+    if (not lstat($file)) {
+        return _g('nonexistent') if $! == ENOENT;
+        syserr(_g('cannot stat %s'), $file);
+    } else {
+        -f _ && return _g('plain file');
+        -d _ && return _g('directory');
+        -l _ && return sprintf(_g('symlink to %s'), readlink($file));
+        -b _ && return _g('block device');
+        -c _ && return _g('character device');
+        -p _ && return _g('named pipe');
+        -S _ && return _g('named socket');
+    }
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Source/Quilt.pm b/third_party/dpkg-dev/scripts/Dpkg/Source/Quilt.pm
new file mode 100644
index 0000000..7716927
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Source/Quilt.pm
@@ -0,0 +1,308 @@
+# Copyright © 2008-2012 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Source::Quilt;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Source::Patch;
+use Dpkg::Source::Functions qw(erasedir fs_time);
+use Dpkg::Vendor qw(get_current_vendor);
+
+use File::Spec;
+use File::Copy;
+use File::Find;
+use File::Path qw(make_path);
+use File::Basename;
+
+sub new {
+    my ($this, $dir, %opts) = @_;
+    my $class = ref($this) || $this;
+
+    my $self = {
+        dir => $dir,
+    };
+    bless $self, $class;
+
+    $self->load_series();
+    $self->load_db();
+
+    return $self;
+}
+
+sub setup_db {
+    my ($self) = @_;
+    my $db_dir = $self->get_db_file();
+    if (not -d $db_dir) {
+        mkdir $db_dir or syserr(_g('cannot mkdir %s'), $db_dir);
+    }
+    my $file = $self->get_db_file('.version');
+    if (not -e $file) {
+        open(my $version_fh, '>', $file) or syserr(_g('cannot write %s'), $file);
+        print { $version_fh } "2\n";
+        close($version_fh);
+    }
+    # The files below are used by quilt to know where patches are stored
+    # and what file contains the patch list (supported by quilt >= 0.48-5
+    # in Debian).
+    $file = $self->get_db_file('.quilt_patches');
+    if (not -e $file) {
+        open(my $qpatch_fh, '>', $file) or syserr(_g('cannot write %s'), $file);
+        print { $qpatch_fh } "debian/patches\n";
+        close($qpatch_fh);
+    }
+    $file = $self->get_db_file('.quilt_series');
+    if (not -e $file) {
+        open(my $qseries_fh, '>', $file) or syserr(_g('cannot write %s'), $file);
+        my $series = $self->get_series_file();
+        $series = (File::Spec->splitpath($series))[2];
+        print { $qseries_fh } "$series\n";
+        close($qseries_fh);
+    }
+}
+
+sub load_db {
+    my ($self) = @_;
+
+    my $pc_applied = $self->get_db_file('applied-patches');
+    $self->{applied_patches} = [ $self->read_patch_list($pc_applied) ];
+}
+
+sub write_db {
+    my ($self) = @_;
+
+    $self->setup_db();
+    my $pc_applied = $self->get_db_file('applied-patches');
+    open(my $applied_fh, '>', $pc_applied) or
+        syserr(_g('cannot write %s'), $pc_applied);
+    foreach my $patch (@{$self->{applied_patches}}) {
+        print { $applied_fh } "$patch\n";
+    }
+    close($applied_fh);
+}
+
+sub load_series {
+    my ($self, %opts) = @_;
+
+    my $series = $self->get_series_file();
+    $self->{series} = [ $self->read_patch_list($series, %opts) ];
+}
+
+sub series {
+    my ($self) = @_;
+    return @{$self->{series}};
+}
+
+sub applied {
+    my ($self) = @_;
+    return @{$self->{applied_patches}};
+}
+
+sub top {
+    my ($self) = @_;
+    my $count = scalar @{$self->{applied_patches}};
+    return $self->{applied_patches}[$count - 1] if $count;
+    return;
+}
+
+sub next {
+    my ($self) = @_;
+    my $count_applied = scalar @{$self->{applied_patches}};
+    my $count_series = scalar @{$self->{series}};
+    return $self->{series}[$count_applied] if ($count_series > $count_applied);
+    return;
+}
+
+sub push {
+    my ($self, %opts) = @_;
+    $opts{verbose} //= 0;
+    $opts{timestamp} //= fs_time($self->{dir});
+
+    my $patch = $self->next();
+    return unless defined $patch;
+
+    my $path = $self->get_patch_file($patch);
+    my $obj = Dpkg::Source::Patch->new(filename => $path);
+
+    info(_g('applying %s'), $patch) if $opts{verbose};
+    eval {
+        $obj->apply($self->{dir}, timestamp => $opts{timestamp},
+                    verbose => $opts{verbose},
+                    force_timestamp => 1, create_dirs => 1, remove_backup => 0,
+                    options => [ '-t', '-F', '0', '-N', '-p1', '-u',
+                                 '-V', 'never', '-g0', '-E', '-b',
+                                 '-B', ".pc/$patch/", '--reject-file=-' ]);
+    };
+    if ($@) {
+        info(_g('fuzz is not allowed when applying patches'));
+        info(_g("if patch '%s' is correctly applied by quilt, use '%s' to update it"),
+             $patch, 'quilt refresh');
+        $self->restore_quilt_backup_files($patch, %opts);
+        erasedir($self->get_db_file($patch));
+        die $@;
+    }
+    CORE::push @{$self->{applied_patches}}, $patch;
+    $self->write_db();
+}
+
+sub pop {
+    my ($self, %opts) = @_;
+    $opts{verbose} //= 0;
+    $opts{timestamp} //= fs_time($self->{dir});
+    $opts{reverse_apply} //= 0;
+
+    my $patch = $self->top();
+    return unless defined $patch;
+
+    info(_g('unapplying %s'), $patch) if $opts{verbose};
+    my $backup_dir = $self->get_db_file($patch);
+    if (-d $backup_dir and not $opts{reverse_apply}) {
+        # Use the backup copies to restore
+        $self->restore_quilt_backup_files($patch);
+    } else {
+        # Otherwise reverse-apply the patch
+        my $path = $self->get_patch_file($patch);
+        my $obj = Dpkg::Source::Patch->new(filename => $path);
+
+        $obj->apply($self->{dir}, timestamp => $opts{timestamp},
+                    verbose => 0, force_timestamp => 1, remove_backup => 0,
+                    options => [ '-R', '-t', '-N', '-p1',
+                                 '-u', '-V', 'never', '-g0', '-E',
+                                 '--no-backup-if-mismatch' ]);
+    }
+
+    erasedir($backup_dir);
+    pop @{$self->{applied_patches}};
+    $self->write_db();
+}
+
+sub get_db_version {
+    my ($self) = @_;
+    my $pc_ver = $self->get_db_file('.version');
+    if (-f $pc_ver) {
+        open(my $ver_fh, '<', $pc_ver) or syserr(_g('cannot read %s'), $pc_ver);
+        my $version = <$ver_fh>;
+        chomp $version;
+        close($ver_fh);
+        return $version;
+    }
+    return;
+}
+
+sub find_problems {
+    my ($self) = @_;
+    my $patch_dir = $self->get_patch_file();
+    if (-e $patch_dir and not -d _) {
+        return sprintf(_g('%s should be a directory or non-existing'), $patch_dir);
+    }
+    my $series = $self->get_series_file();
+    if (-e $series and not -f _) {
+        return sprintf(_g('%s should be a file or non-existing'), $series);
+    }
+    return;
+}
+
+sub get_series_file {
+    my ($self) = @_;
+    my $vendor = lc(get_current_vendor() || 'debian');
+    # Series files are stored alongside patches
+    my $default_series = $self->get_patch_file('series');
+    my $vendor_series = $self->get_patch_file("$vendor.series");
+    return $vendor_series if -e $vendor_series;
+    return $default_series;
+}
+
+sub get_db_file {
+    my $self = shift;
+    return File::Spec->catfile($self->{dir}, '.pc', @_);
+}
+
+sub get_db_dir {
+    my ($self) = @_;
+    return $self->get_db_file();
+}
+
+sub get_patch_file {
+    my $self = shift;
+    return File::Spec->catfile($self->{dir}, 'debian', 'patches', @_);
+}
+
+sub get_patch_dir {
+    my ($self) = @_;
+    return $self->get_patch_file();
+}
+
+## METHODS BELOW ARE INTERNAL ##
+
+sub read_patch_list {
+    my ($self, $file, %opts) = @_;
+    return () if not defined $file or not -f $file;
+    $opts{warn_options} //= 0;
+    my @patches;
+    open(my $series_fh, '<' , $file) or syserr(_g('cannot read %s'), $file);
+    while (defined($_ = <$series_fh>)) {
+        chomp; s/^\s+//; s/\s+$//; # Strip leading/trailing spaces
+        s/(^|\s+)#.*$//; # Strip comment
+        next unless $_;
+        if (/^(\S+)\s+(.*)$/) {
+            $_ = $1;
+            if ($2 ne '-p1') {
+                warning(_g('the series file (%s) contains unsupported ' .
+                           "options ('%s', line %s); dpkg-source might " .
+                           'fail when applying patches'),
+                        $file, $2, $.) if $opts{warn_options};
+            }
+        }
+        error(_g('%s contains an insecure path: %s'), $file, $_) if m{(^|/)\.\./};
+        CORE::push @patches, $_;
+    }
+    close($series_fh);
+    return @patches;
+}
+
+sub restore_quilt_backup_files {
+    my ($self, $patch, %opts) = @_;
+    my $patch_dir = $self->get_db_file($patch);
+    return unless -d $patch_dir;
+    info(_g('restoring quilt backup files for %s'), $patch) if $opts{verbose};
+    find({
+        no_chdir => 1,
+        wanted => sub {
+            return if -d $_;
+            my $relpath_in_srcpkg = File::Spec->abs2rel($_, $patch_dir);
+            my $target = File::Spec->catfile($self->{dir}, $relpath_in_srcpkg);
+            if (-s $_) {
+                unlink($target);
+                make_path(dirname($target));
+                unless (link($_, $target)) {
+                    copy($_, $target)
+                        or syserr(_g('failed to copy %s to %s'), $_, $target);
+                    chmod((stat(_))[2], $target)
+                        or syserr(_g("unable to change permission of `%s'"), $target);
+                }
+            } else {
+                # empty files are "backups" for new files that patch created
+                unlink($target);
+            }
+        }
+    }, $patch_dir);
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Substvars.pm b/third_party/dpkg-dev/scripts/Dpkg/Substvars.pm
new file mode 100644
index 0000000..e287dc9f
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Substvars.pm
@@ -0,0 +1,337 @@
+# Copyright © 2006-2009,2012 Guillem Jover <guillem@debian.org>
+# Copyright © 2007-2010 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Substvars;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+use Dpkg ();
+use Dpkg::Arch qw(get_host_arch);
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use Carp;
+use POSIX qw(:errno_h);
+
+use parent qw(Dpkg::Interface::Storable);
+
+my $maxsubsts = 50;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Substvars - handle variable substitution in strings
+
+=head1 DESCRIPTION
+
+It provides some an object which is able to substitute variables in
+strings.
+
+=head1 METHODS
+
+=over 8
+
+=item my $s = Dpkg::Substvars->new($file)
+
+Create a new object that can do substitutions. By default it contains
+generic substitutions like ${Newline}, ${Space}, ${Tab}, ${dpkg:Version}
+and ${dpkg:Upstream-Version}.
+
+Additional substitutions will be read from the $file passed as parameter.
+
+It keeps track of which substitutions were actually used (only counting
+substvars(), not get()), and warns about unused substvars when asked to. The
+substitutions that are always present are not included in these warnings.
+
+=cut
+
+sub new {
+    my ($this, $arg) = @_;
+    my $class = ref($this) || $this;
+    my $self = {
+        vars => {
+            'Newline' => "\n",
+            'Space' => ' ',
+            'Tab' => "\t",
+            'dpkg:Version' => $Dpkg::PROGVERSION,
+            'dpkg:Upstream-Version' => $Dpkg::PROGVERSION,
+            },
+        used => {},
+	msg_prefix => '',
+    };
+    $self->{vars}{'dpkg:Upstream-Version'} =~ s/-[^-]+$//;
+    bless $self, $class;
+    $self->mark_as_used($_) foreach keys %{$self->{vars}};
+    if ($arg) {
+        $self->load($arg) if -e $arg;
+    }
+    return $self;
+}
+
+=item $s->set($key, $value)
+
+Add/replace a substitution.
+
+=cut
+
+sub set {
+    my ($self, $key, $value) = @_;
+    $self->{vars}{$key} = $value;
+}
+
+=item $s->set_as_used($key, $value)
+
+Add/replace a substitution and mark it as used (no warnings will be produced
+even if unused).
+
+=cut
+
+sub set_as_used {
+    my ($self, $key, $value) = @_;
+    $self->set($key, $value);
+    $self->mark_as_used($key);
+}
+
+=item $s->get($key)
+
+Get the value of a given substitution.
+
+=cut
+
+sub get {
+    my ($self, $key) = @_;
+    return $self->{vars}{$key};
+}
+
+=item $s->delete($key)
+
+Remove a given substitution.
+
+=cut
+
+sub delete {
+    my ($self, $key) = @_;
+    delete $self->{used}{$key};
+    return delete $self->{vars}{$key};
+}
+
+=item $s->mark_as_used($key)
+
+Prevents warnings about a unused substitution, for example if it is provided by
+default.
+
+=cut
+
+sub mark_as_used {
+    my ($self, $key) = @_;
+    $self->{used}{$key}++;
+}
+
+=item $s->no_warn($key)
+
+Obsolete function, use mark_as_used() instead.
+
+=cut
+
+sub no_warn {
+    my ($self, $key) = @_;
+    carp 'obsolete no_warn() function, use mark_as_used() instead';
+    $self->mark_as_used($key);
+}
+
+=item $s->load($file)
+
+Add new substitutions read from $file.
+
+=item $s->parse($fh, $desc)
+
+Add new substitutions read from the filehandle. $desc is used to identify
+the filehandle in error messages.
+
+=cut
+
+sub parse {
+    my ($self, $fh, $varlistfile) = @_;
+    binmode($fh);
+    while (<$fh>) {
+	next if m/^\s*\#/ || !m/\S/;
+	s/\s*\n$//;
+	if (! m/^(\w[-:0-9A-Za-z]*)\=(.*)$/) {
+	    error(_g('bad line in substvars file %s at line %d'),
+		  $varlistfile, $.);
+	}
+	$self->{vars}{$1} = $2;
+    }
+}
+
+=item $s->set_version_substvars($sourceversion, $binaryversion)
+
+Defines ${binary:Version}, ${source:Version} and
+${source:Upstream-Version} based on the given version strings.
+
+These will never be warned about when unused.
+
+=cut
+
+sub set_version_substvars {
+    my ($self, $sourceversion, $binaryversion) = @_;
+
+    # Handle old function signature taking only one argument.
+    $binaryversion ||= $sourceversion;
+
+    # For backwards compatibility on binNMUs that do not use the Binary-Only
+    # field on the changelog, always fix up the source version.
+    $sourceversion =~ s/\+b[0-9]+$//;
+
+    $self->{vars}{'binary:Version'} = $binaryversion;
+    $self->{vars}{'source:Version'} = $sourceversion;
+    $self->{vars}{'source:Upstream-Version'} = $sourceversion;
+    $self->{vars}{'source:Upstream-Version'} =~ s/-[^-]*$//;
+
+    # XXX: Source-Version is now deprecated, remove in the future.
+    $self->{vars}{'Source-Version'} = $binaryversion;
+
+    $self->mark_as_used($_) foreach qw/binary:Version source:Version source:Upstream-Version Source-Version/;
+}
+
+=item $s->set_arch_substvars()
+
+Defines architecture variables: ${Arch}.
+
+This will never be warned about when unused.
+
+=cut
+
+sub set_arch_substvars {
+    my ($self) = @_;
+
+    $self->set_as_used('Arch', get_host_arch());
+}
+
+=item $newstring = $s->substvars($string)
+
+Substitutes variables in $string and return the result in $newstring.
+
+=cut
+
+sub substvars {
+    my ($self, $v, %opts) = @_;
+    my $lhs;
+    my $vn;
+    my $rhs = '';
+    my $count = 0;
+    $opts{msg_prefix} = $self->{msg_prefix} unless exists $opts{msg_prefix};
+    $opts{no_warn} = 0 unless exists $opts{no_warn};
+
+    while ($v =~ m/^(.*?)\$\{([-:0-9a-z]+)\}(.*)$/si) {
+        # If we have consumed more from the leftover data, then
+        # reset the recursive counter.
+        $count = 0 if (length($3) < length($rhs));
+
+        if ($count >= $maxsubsts) {
+            error($opts{msg_prefix} .
+	          _g("too many substitutions - recursive ? - in \`%s'"), $v);
+        }
+        $lhs = $1; $vn = $2; $rhs = $3;
+        if (defined($self->{vars}{$vn})) {
+            $v = $lhs . $self->{vars}{$vn} . $rhs;
+            $self->mark_as_used($vn);
+            $count++;
+        } else {
+            warning($opts{msg_prefix} . _g('unknown substitution variable ${%s}'),
+	            $vn) unless $opts{no_warn};
+            $v = $lhs . $rhs;
+        }
+    }
+    return $v;
+}
+
+=item $s->warn_about_unused()
+
+Issues warning about any variables that were set, but not used
+
+=cut
+
+sub warn_about_unused {
+    my ($self, %opts) = @_;
+    $opts{msg_prefix} = $self->{msg_prefix} unless exists $opts{msg_prefix};
+
+    foreach my $vn (keys %{$self->{vars}}) {
+        next if $self->{used}{$vn};
+        # Empty substitutions variables are ignored on the basis
+        # that they are not required in the current situation
+        # (example: debhelper's misc:Depends in many cases)
+        next if $self->{vars}{$vn} eq '';
+        warning($opts{msg_prefix} . _g('unused substitution variable ${%s}'),
+                $vn);
+    }
+}
+
+=item $s->set_msg_prefix($prefix)
+
+Define a prefix displayed before all warnings/error messages output
+by the module.
+
+=cut
+
+sub set_msg_prefix {
+    my ($self, $prefix) = @_;
+    $self->{msg_prefix} = $prefix;
+}
+
+=item $s->save($file)
+
+Store all substitutions variables except the automatic ones in the
+indicated file.
+
+=item "$s"
+
+Return a string representation of all substitutions variables except the
+automatic ones.
+
+=item $str = $s->output($fh)
+
+Print all substitutions variables except the automatic ones in the
+filehandle and return the content written.
+
+=cut
+
+sub output {
+    my ($self, $fh) = @_;
+    my $str = '';
+    # Store all non-automatic substitutions only
+    foreach my $vn (sort keys %{$self->{vars}}) {
+	next if /^(?:(?:dpkg|source|binary):(?:Source-)?Version|Space|Tab|Newline|Arch|Source-Version|F:.+)$/;
+	my $line = "$vn=" . $self->{vars}{$vn} . "\n";
+	print { $fh } $line if defined $fh;
+	$str .= $line;
+    }
+    return $str;
+}
+
+=back
+
+=head1 AUTHOR
+
+Raphaël Hertzog <hertzog@debian.org>.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Util.pm b/third_party/dpkg-dev/scripts/Dpkg/Util.pm
new file mode 100644
index 0000000..504abd5e
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Util.pm
@@ -0,0 +1,51 @@
+# Copyright © 2013 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Util;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(any none);
+our %EXPORT_TAGS = (list => [ qw(any none) ]);
+
+# XXX: Ideally we would use List::MoreUtils, but that's not a core module,
+# so to avoid the additional dependency we'll make do with the following
+# trivial reimplementations.
+
+sub any(&@) {
+    my $code = shift;
+
+    foreach (@_) {
+        return 1 if $code->();
+    }
+
+    return 0;
+}
+
+sub none(&@) {
+    my $code = shift;
+
+    foreach (@_) {
+        return 0 if $code->();
+    }
+
+    return 1;
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Vars.pm b/third_party/dpkg-dev/scripts/Dpkg/Vars.pm
new file mode 100644
index 0000000..0186bcd
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Vars.pm
@@ -0,0 +1,50 @@
+# Copyright © 2007-2009,2012-2013 Guillem Jover <guillem@debian.org>
+# Copyright © 2007 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Vars;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.03';
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::Package;
+
+use Exporter qw(import);
+our @EXPORT = qw(get_source_package set_source_package);
+
+my $sourcepackage;
+
+sub get_source_package {
+    return $sourcepackage;
+}
+
+sub set_source_package {
+    my $v = shift;
+    my $err = pkg_name_is_illegal($v);
+    error(_g("source package name '%s' is illegal: %s"), $v, $err) if $err;
+
+    if (not defined($sourcepackage)) {
+        $sourcepackage = $v;
+    } elsif ($v ne $sourcepackage) {
+        error(_g('source package has two conflicting values - %s and %s'),
+              $sourcepackage, $v);
+    }
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Vendor.pm b/third_party/dpkg-dev/scripts/Dpkg/Vendor.pm
new file mode 100644
index 0000000..96d0976
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Vendor.pm
@@ -0,0 +1,182 @@
+# Copyright © 2008-2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Vendor;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg ();
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::BuildEnv;
+use Dpkg::Control::HashCore;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(get_vendor_info get_current_vendor get_vendor_file
+                    get_vendor_dir get_vendor_object run_vendor_hook);
+
+my $origins = "$Dpkg::CONFDIR/origins";
+$origins = $ENV{DPKG_ORIGINS_DIR} if $ENV{DPKG_ORIGINS_DIR};
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Vendor - get access to some vendor specific information
+
+=head1 DESCRIPTION
+
+The files in $Dpkg::CONFDIR/origins/ can provide information about various
+vendors who are providing Debian packages. Currently those files look like
+this:
+
+  Vendor: Debian
+  Vendor-URL: http://www.debian.org/
+  Bugs: debbugs://bugs.debian.org
+
+If the vendor derives from another vendor, the file should document
+the relationship by listing the base distribution in the Parent field:
+
+  Parent: Debian
+
+The file should be named according to the vendor name.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item $dir = Dpkg::Vendor::get_vendor_dir()
+
+Returns the current dpkg origins directory name, where the vendor files
+are stored.
+
+=cut
+
+sub get_vendor_dir {
+    return $origins;
+}
+
+=item $fields = Dpkg::Vendor::get_vendor_info($name)
+
+Returns a Dpkg::Control object with the information parsed from the
+corresponding vendor file in $Dpkg::CONFDIR/origins/. If $name is omitted,
+it will use $Dpkg::CONFDIR/origins/default which is supposed to be a symlink
+to the vendor of the currently installed operating system. Returns undef
+if there's no file for the given vendor.
+
+=cut
+
+sub get_vendor_info(;$) {
+    my $vendor = shift || 'default';
+    my $file = get_vendor_file($vendor);
+    return unless $file;
+    my $fields = Dpkg::Control::HashCore->new();
+    $fields->load($file) or error(_g('%s is empty'), $file);
+    return $fields;
+}
+
+=item $name = Dpkg::Vendor::get_vendor_file($name)
+
+Check if there's a file for the given vendor and returns its
+name.
+
+=cut
+
+sub get_vendor_file(;$) {
+    my $vendor = shift || 'default';
+    my $file;
+    my @tries = ($vendor, lc($vendor), ucfirst($vendor), ucfirst(lc($vendor)));
+    if ($vendor =~ s/\s+/-/) {
+        push @tries, $vendor, lc($vendor), ucfirst($vendor), ucfirst(lc($vendor));
+    }
+    foreach my $name (@tries) {
+        $file = "$origins/$name" if -e "$origins/$name";
+    }
+    return $file;
+}
+
+=item $name = Dpkg::Vendor::get_current_vendor()
+
+Returns the name of the current vendor. If DEB_VENDOR is set, it uses
+that first, otherwise it falls back to parsing $Dpkg::CONFDIR/origins/default.
+If that file doesn't exist, it returns undef.
+
+=cut
+
+sub get_current_vendor() {
+    my $f;
+    if (Dpkg::BuildEnv::has('DEB_VENDOR')) {
+        $f = get_vendor_info(Dpkg::BuildEnv::get('DEB_VENDOR'));
+        return $f->{'Vendor'} if defined $f;
+    }
+    $f = get_vendor_info();
+    return $f->{'Vendor'} if defined $f;
+    return;
+}
+
+=item $object = Dpkg::Vendor::get_vendor_object($name)
+
+Return the Dpkg::Vendor::* object of the corresponding vendor.
+If $name is omitted, return the object of the current vendor.
+If no vendor can be identified, then return the Dpkg::Vendor::Default
+object.
+
+=cut
+
+my %OBJECT_CACHE;
+sub get_vendor_object {
+    my $vendor = shift || get_current_vendor() || 'Default';
+    return $OBJECT_CACHE{$vendor} if exists $OBJECT_CACHE{$vendor};
+
+    my ($obj, @names);
+    if ($vendor ne 'Default') {
+        push @names, $vendor, lc($vendor), ucfirst($vendor), ucfirst(lc($vendor));
+    }
+    foreach my $name (@names, 'Default') {
+        eval qq{
+            require Dpkg::Vendor::$name;
+            \$obj = Dpkg::Vendor::$name->new();
+        };
+        last unless $@;
+    }
+    $OBJECT_CACHE{$vendor} = $obj;
+    return $obj;
+}
+
+=item Dpkg::Vendor::run_vendor_hook($hookid, @params)
+
+Run a hook implemented by the current vendor object.
+
+=cut
+
+sub run_vendor_hook {
+    my $vendor_obj = get_vendor_object();
+    $vendor_obj->run_hook(@_);
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New function: get_vendor_dir().
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Vendor/Debian.pm b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Debian.pm
new file mode 100644
index 0000000..9cce36d5
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Debian.pm
@@ -0,0 +1,194 @@
+# Copyright © 2009-2011 Raphaël Hertzog <hertzog@debian.org>
+#
+# Hardening build flags handling derived from work of:
+# Copyright © 2009-2011 Kees Cook <kees@debian.org>
+# Copyright © 2007-2008 Canonical, Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Vendor::Debian;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use parent qw(Dpkg::Vendor::Default);
+
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Control::Types;
+use Dpkg::BuildOptions;
+use Dpkg::Arch qw(get_host_arch debarch_to_debtriplet);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Vendor::Debian - Debian vendor object
+
+=head1 DESCRIPTION
+
+This vendor object customize the behaviour of dpkg scripts
+for Debian specific actions.
+
+=cut
+
+sub run_hook {
+    my ($self, $hook, @params) = @_;
+
+    if ($hook eq 'keyrings') {
+        return ('/usr/share/keyrings/debian-keyring.gpg',
+                '/usr/share/keyrings/debian-maintainers.gpg');
+    } elsif ($hook eq 'register-custom-fields') {
+    } elsif ($hook eq 'extend-patch-header') {
+        my ($textref, $ch_info) = @params;
+	if ($ch_info->{'Closes'}) {
+	    foreach my $bug (split(/\s+/, $ch_info->{'Closes'})) {
+		$$textref .= "Bug-Debian: http://bugs.debian.org/$bug\n";
+	    }
+	}
+
+	# XXX: Layer violation...
+	require Dpkg::Vendor::Ubuntu;
+	my $b = Dpkg::Vendor::Ubuntu::find_launchpad_closes($ch_info->{'Changes'});
+	foreach my $bug (@$b) {
+	    $$textref .= "Bug-Ubuntu: https://bugs.launchpad.net/bugs/$bug\n";
+	}
+    } elsif ($hook eq 'update-buildflags') {
+	$self->add_hardening_flags(@params);
+    } else {
+        return $self->SUPER::run_hook($hook, @params);
+    }
+}
+
+sub add_hardening_flags {
+    my ($self, $flags) = @_;
+    my $arch = get_host_arch();
+    my ($abi, $os, $cpu) = debarch_to_debtriplet($arch);
+
+    unless (defined $abi and defined $os and defined $cpu) {
+        warning(_g("unknown host architecture '%s'"), $arch);
+        ($abi, $os, $cpu) = ('', '', '');
+    }
+
+    # Features enabled by default for all builds.
+    my %use_feature = (
+	pie => 0,
+	stackprotector => 1,
+	fortify => 1,
+	format => 1,
+	relro => 1,
+	bindnow => 0,
+    );
+
+    # Adjust features based on Maintainer's desires.
+    my $opts = Dpkg::BuildOptions->new(envvar => 'DEB_BUILD_MAINT_OPTIONS');
+    foreach my $feature (split(/,/, $opts->get('hardening') // '')) {
+	$feature = lc($feature);
+	if ($feature =~ s/^([+-])//) {
+	    my $value = ($1 eq '+') ? 1 : 0;
+	    if ($feature eq 'all') {
+		$use_feature{$_} = $value foreach keys %use_feature;
+	    } else {
+		if (exists $use_feature{$feature}) {
+		    $use_feature{$feature} = $value;
+		} else {
+		    warning(_g('unknown hardening feature: %s'), $feature);
+		}
+	    }
+	} else {
+	    warning(_g('incorrect value in hardening option of ' .
+	               'DEB_BUILD_MAINT_OPTIONS: %s'), $feature);
+	}
+    }
+
+    # Mask features that are not available on certain architectures.
+    if ($os !~ /^(linux|knetbsd|hurd)$/ or
+	$cpu =~ /^(hppa|mips|mipsel|avr32)$/) {
+	# Disabled on non-linux/knetbsd/hurd (see #430455 and #586215).
+	# Disabled on hppa, mips/mipsel (#532821), avr32
+	#  (#574716).
+	$use_feature{pie} = 0;
+    }
+    if ($cpu =~ /^(ia64|alpha|mips|mipsel|hppa)$/ or $arch eq 'arm') {
+	# Stack protector disabled on ia64, alpha, mips, mipsel, hppa.
+	#   "warning: -fstack-protector not supported for this target"
+	# Stack protector disabled on arm (ok on armel).
+	#   compiler supports it incorrectly (leads to SEGV)
+	$use_feature{stackprotector} = 0;
+    }
+    if ($cpu =~ /^(ia64|hppa|avr32)$/) {
+	# relro not implemented on ia64, hppa, avr32.
+	$use_feature{relro} = 0;
+    }
+
+    # Mask features that might be influenced by other flags.
+    if ($flags->{build_options}->has('noopt')) {
+      # glibc 2.16 and later warn when using -O0 and _FORTIFY_SOURCE.
+      $use_feature{fortify} = 0;
+    }
+
+    # Handle logical feature interactions.
+    if ($use_feature{relro} == 0) {
+	# Disable bindnow if relro is not enabled, since it has no
+	# hardening ability without relro and may incur load penalties.
+	$use_feature{bindnow} = 0;
+    }
+
+    # PIE
+    if ($use_feature{pie}) {
+	$flags->append('CFLAGS', '-fPIE');
+	$flags->append('FFLAGS', '-fPIE');
+	$flags->append('CXXFLAGS', '-fPIE');
+	$flags->append('GCJFLAGS', '-fPIE');
+	$flags->append('LDFLAGS', '-fPIE -pie');
+    }
+
+    # Stack protector
+    if ($use_feature{stackprotector}) {
+	$flags->append('CFLAGS', '-fstack-protector --param=ssp-buffer-size=4');
+	$flags->append('FFLAGS', '-fstack-protector --param=ssp-buffer-size=4');
+	$flags->append('CXXFLAGS', '-fstack-protector --param=ssp-buffer-size=4');
+	$flags->append('GCJFLAGS', '-fstack-protector --param=ssp-buffer-size=4');
+    }
+
+    # Fortify Source
+    if ($use_feature{fortify}) {
+	$flags->append('CPPFLAGS', '-D_FORTIFY_SOURCE=2');
+    }
+
+    # Format Security
+    if ($use_feature{format}) {
+	$flags->append('CFLAGS', '-Wformat -Werror=format-security');
+	$flags->append('CXXFLAGS', '-Wformat -Werror=format-security');
+    }
+
+    # Read-only Relocations
+    if ($use_feature{relro}) {
+	$flags->append('LDFLAGS', '-Wl,-z,relro');
+    }
+
+    # Bindnow
+    if ($use_feature{bindnow}) {
+	$flags->append('LDFLAGS', '-Wl,-z,now');
+    }
+
+    # Store the feature usage.
+    while (my ($feature, $enabled) = each %use_feature) {
+	$flags->set_feature('hardening', $feature, $enabled);
+    }
+}
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Vendor/Default.pm b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Default.pm
new file mode 100644
index 0000000..f2d8aff
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Default.pm
@@ -0,0 +1,136 @@
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Vendor::Default;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+# If you use this file as template to create a new vendor object, please
+# uncomment the following lines
+#use parent qw(Dpkg::Vendor::Default);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Vendor::Default - default vendor object
+
+=head1 DESCRIPTION
+
+A vendor object is used to provide vendor specific behaviour
+in various places. This is the default object used in case
+there's none for the current vendor or in case the vendor could
+not be identified (see Dpkg::Vendor documentation).
+
+It provides some hooks that are called by various dpkg-* tools.
+If you need a new hook, please file a bug against dpkg-dev and explain
+your need. Note that the hook API has no guaranty to be stable over an
+extended period. If you run an important distribution that makes use
+of vendor hooks, you'd better submit them for integration so that
+we avoid breaking your code.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item $vendor_obj = Dpkg::Vendor::Default->new()
+
+Creates the default vendor object. Can be inherited by all vendor objects
+if they don't need any specific initialization at object creation time.
+
+=cut
+
+sub new {
+    my ($this) = @_;
+    my $class = ref($this) || $this;
+    my $self = {};
+    bless $self, $class;
+    return $self;
+}
+
+=item $vendor_obj->run_hook($id, @params)
+
+Run the corresponding hook. The parameters are hook-specific. The
+supported hooks are:
+
+=over 8
+
+=item before-source-build ($srcpkg)
+
+The first parameter is a Dpkg::Source::Package object. The hook is called
+just before the execution of $srcpkg->build().
+
+=item keyrings ()
+
+The hook is called when dpkg-source is checking a signature on a source
+package. It takes no parameters, but returns a (possibly empty) list of
+vendor-specific keyrings.
+
+=item register-custom-fields ()
+
+The hook is called in Dpkg::Control::Fields to register custom fields.
+You should return a list of arrays. Each array is an operation to perform.
+The first item is the name of the operation and corresponds
+to a field_* function provided by Dpkg::Control::Fields. The remaining
+fields are the parameters that are passed unchanged to the corresponding
+function.
+
+Known operations are "register", "insert_after" and "insert_before".
+
+=item post-process-changelog-entry ($fields)
+
+The hook is called in Dpkg::Changelog to post-process a
+Dpkg::Changelog::Entry after it has been created and filled with the
+appropriate values.
+
+=item update-buildflags ($flags)
+
+The hook is called in Dpkg::BuildFlags to allow the vendor to override
+the default values set for the various build flags. $flags is a
+Dpkg::BuildFlags object.
+
+=back
+
+=cut
+
+sub run_hook {
+    my ($self, $hook, @params) = @_;
+
+    if ($hook eq 'before-source-build') {
+        my $srcpkg = shift @params;
+    } elsif ($hook eq 'keyrings') {
+        return ();
+    } elsif ($hook eq 'register-custom-fields') {
+        return ();
+    } elsif ($hook eq 'post-process-changelog-entry') {
+        my $fields = shift @params;
+    } elsif ($hook eq 'extend-patch-header') {
+	my ($textref, $ch_info) = @params;
+    } elsif ($hook eq 'update-buildflags') {
+	my $flags = shift @params;
+    }
+
+    # Default return value for unknown/unimplemented hooks
+    return;
+}
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Vendor/Ubuntu.pm b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Ubuntu.pm
new file mode 100644
index 0000000..e9c2003
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Vendor/Ubuntu.pm
@@ -0,0 +1,185 @@
+# Copyright © 2008 Ian Jackson <ian@davenant.greenend.org.uk>
+# Copyright © 2008 Canonical, Ltd.
+#   written by Colin Watson <cjwatson@ubuntu.com>
+# Copyright © 2008 James Westby <jw+debian@jameswestby.net>
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Vendor::Ubuntu;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::Path qw(find_command);
+use Dpkg::Control::Types;
+use Dpkg::BuildOptions;
+use Dpkg::Arch qw(debarch_eq get_host_arch);
+
+use parent qw(Dpkg::Vendor::Debian);
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Vendor::Ubuntu - Ubuntu vendor object
+
+=head1 DESCRIPTION
+
+This vendor object customize the behaviour of dpkg-source
+to check that Maintainers have been modified if necessary.
+
+=cut
+
+sub run_hook {
+    my ($self, $hook, @params) = @_;
+
+    if ($hook eq 'before-source-build') {
+        my $src = shift @params;
+        my $fields = $src->{fields};
+
+        # check that Maintainer/XSBC-Original-Maintainer comply to
+        # https://wiki.ubuntu.com/DebianMaintainerField
+        if (defined($fields->{'Version'}) and defined($fields->{'Maintainer'}) and
+           $fields->{'Version'} =~ /ubuntu/) {
+           if ($fields->{'Maintainer'} !~ /ubuntu/i) {
+               if (defined ($ENV{DEBEMAIL}) and $ENV{DEBEMAIL} =~ /\@ubuntu\.com/) {
+                   error(_g('Version number suggests Ubuntu changes, but Maintainer: does not have Ubuntu address'));
+               } else {
+                   warning(_g('Version number suggests Ubuntu changes, but Maintainer: does not have Ubuntu address'));
+               }
+           }
+           unless ($fields->{'Original-Maintainer'}) {
+               warning(_g('Version number suggests Ubuntu changes, but there is no XSBC-Original-Maintainer field'));
+           }
+        }
+
+    } elsif ($hook eq 'keyrings') {
+        my @keyrings = $self->SUPER::run_hook($hook);
+
+        push(@keyrings, '/usr/share/keyrings/ubuntu-archive-keyring.gpg');
+        return @keyrings;
+
+    } elsif ($hook eq 'register-custom-fields') {
+        my @field_ops = $self->SUPER::run_hook($hook);
+        push @field_ops,
+            [ 'register', 'Launchpad-Bugs-Fixed',
+              CTRL_FILE_CHANGES | CTRL_CHANGELOG  ],
+            [ 'insert_after', CTRL_FILE_CHANGES, 'Closes', 'Launchpad-Bugs-Fixed' ],
+            [ 'insert_after', CTRL_CHANGELOG, 'Closes', 'Launchpad-Bugs-Fixed' ];
+        return @field_ops;
+
+    } elsif ($hook eq 'post-process-changelog-entry') {
+        my $fields = shift @params;
+
+        # Add Launchpad-Bugs-Fixed field
+        my $bugs = find_launchpad_closes($fields->{'Changes'} || '');
+        if (scalar(@$bugs)) {
+            $fields->{'Launchpad-Bugs-Fixed'} = join(' ', @$bugs);
+        }
+
+    } elsif ($hook eq 'update-buildflags') {
+	my $flags = shift @params;
+	my $build_opts = Dpkg::BuildOptions->new();
+
+	if (!$build_opts->has('noopt')) {
+	    if (debarch_eq(get_host_arch(), 'ppc64el')) {
+		for my $flag (qw(CFLAGS CXXFLAGS GCJFLAGS FFLAGS)) {
+		    $flags->set($flag, '-g -O3', 'vendor');
+		}
+	    }
+	}
+	# Per https://wiki.ubuntu.com/DistCompilerFlags
+	$flags->set('LDFLAGS', '-Wl,-Bsymbolic-functions', 'vendor');
+
+	# Run the Debian hook to add hardening flags
+	$self->SUPER::run_hook($hook, $flags);
+
+	# Allow control of hardening-wrapper via dpkg-buildpackage DEB_BUILD_OPTIONS
+	my $hardening;
+	if ($build_opts->has('hardening')) {
+	    $hardening = $build_opts->get('hardening') // 1;
+	}
+	if ($build_opts->has('nohardening')) {
+	    $hardening = 0;
+	}
+	if (defined $hardening) {
+	    my $flag = 'DEB_BUILD_HARDENING';
+	    if ($hardening ne '0') {
+		if (!find_command('hardened-cc')) {
+		    syserr(_g("'hardening' flag found but 'hardening-wrapper' not installed"));
+		}
+		if ($hardening ne '1') {
+		    my @options = split(/,\s*/, $hardening);
+		    $hardening = 1;
+
+		    my @hardopts = qw(format fortify stackprotector pie relro);
+		    foreach my $item (@hardopts) {
+			my $upitem = uc($item);
+			foreach my $option (@options) {
+			    if ($option =~ /^(no)?$item$/) {
+				$flags->set($flag . '_' . $upitem,
+				            not defined $1 or $1 eq '', 'env');
+			    }
+			}
+		    }
+		}
+	    }
+	    if (defined $ENV{$flag}) {
+		info(_g('overriding %s in environment: %s'), $flag, $hardening);
+	    }
+	    $flags->set($flag, $hardening, 'env');
+	}
+
+    } else {
+        return $self->SUPER::run_hook($hook, @params);
+    }
+
+}
+
+=head1 PUBLIC FUNCTIONS
+
+=over
+
+=item $bugs = Dpkg::Vendor::Ubuntu::find_launchpad_closes($changes)
+
+Takes one string as argument and finds "LP: #123456, #654321" statements,
+which are references to bugs on Launchpad. Returns all closed bug
+numbers in an array reference.
+
+=cut
+
+sub find_launchpad_closes {
+    my ($changes) = @_;
+    my %closes;
+
+    while ($changes &&
+          ($changes =~ /lp:\s+\#\d+(?:,\s*\#\d+)*/ig)) {
+        $closes{$_} = 1 foreach($& =~ /\#?\s?(\d+)/g);
+    }
+
+    my @closes = sort { $a <=> $b } keys %closes;
+
+    return \@closes;
+}
+
+=back
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/Dpkg/Version.pm b/third_party/dpkg-dev/scripts/Dpkg/Version.pm
new file mode 100644
index 0000000..5461f597
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/Dpkg/Version.pm
@@ -0,0 +1,445 @@
+# Copyright © Colin Watson <cjwatson@debian.org>
+# Copyright © Ian Jackson <iwj@debian.org>
+# Copyright © 2007 Don Armstrong <don@donarmstrong.com>.
+# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Version;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+
+use Carp;
+use Exporter qw(import);
+our @EXPORT = qw(version_compare version_compare_relation
+                 version_normalize_relation version_compare_string
+                 version_compare_part version_split_digits version_check
+                 REL_LT REL_LE REL_EQ REL_GE REL_GT);
+
+use constant {
+    REL_LT => '<<',
+    REL_LE => '<=',
+    REL_EQ => '=',
+    REL_GE => '>=',
+    REL_GT => '>>',
+};
+
+use overload
+    '<=>' => \&comparison,
+    'cmp' => \&comparison,
+    '""'  => sub { return $_[0]->as_string(); },
+    'bool' => sub { return $_[0]->as_string() if $_[0]->is_valid(); },
+    'fallback' => 1;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Version - handling and comparing dpkg-style version numbers
+
+=head1 DESCRIPTION
+
+The Dpkg::Version module provides pure-Perl routines to compare
+dpkg-style version numbers (as used in Debian packages) and also
+an object oriented interface overriding perl operators
+to do the right thing when you compare Dpkg::Version object between
+them.
+
+=head1 OBJECT INTERFACE
+
+=over 4
+
+=item my $v = Dpkg::Version->new($version, %opts)
+
+Create a new Dpkg::Version object corresponding to the version indicated in
+the string (scalar) $version. By default it will accepts any string
+and consider it as a valid version. If you pass the option "check => 1",
+it will return undef if the version is invalid (see version_check for
+details).
+
+You can always call $v->is_valid() later on to verify that the version is
+valid.
+
+=cut
+
+sub new {
+    my ($this, $ver, %opts) = @_;
+    my $class = ref($this) || $this;
+    $ver = "$ver" if ref($ver); # Try to stringify objects
+
+    if ($opts{check}) {
+	return unless version_check($ver);
+    }
+
+    my $self = {};
+    if ($ver =~ /^([^:]*):(.+)$/) {
+	$self->{epoch} = $1;
+	$ver = $2;
+    } else {
+	$self->{epoch} = 0;
+	$self->{no_epoch} = 1;
+    }
+    if ($ver =~ /(.*)-(.*)$/) {
+	$self->{version} = $1;
+	$self->{revision} = $2;
+    } else {
+	$self->{version} = $ver;
+	$self->{revision} = 0;
+	$self->{no_revision} = 1;
+    }
+
+    return bless $self, $class;
+}
+
+=item boolean evaluation
+
+When the Dpkg::Version object is used in a boolean evaluation (for example
+in "if ($v)" or "$v || 'default'") it returns its string representation
+if the version stored is valid ($v->is_valid()) and undef otherwise.
+
+=item $v->is_valid()
+
+Returns true if the version is valid, false otherwise.
+
+=cut
+
+sub is_valid {
+    my ($self) = @_;
+    return scalar version_check($self);
+}
+
+=item $v->epoch(), $v->version(), $v->revision()
+
+Returns the corresponding part of the full version string.
+
+=cut
+
+sub epoch {
+    my $self = shift;
+    return $self->{epoch};
+}
+
+sub version {
+    my $self = shift;
+    return $self->{version};
+}
+
+sub revision {
+    my $self = shift;
+    return $self->{revision};
+}
+
+=item $v->is_native()
+
+Returns true if the version is native, false if it has a revision.
+
+=cut
+
+sub is_native {
+    my $self = shift;
+    return $self->{no_revision};
+}
+
+=item $v1 <=> $v2, $v1 < $v2, $v1 <= $v2, $v1 > $v2, $v1 >= $v2
+
+Numerical comparison of various versions numbers. One of the two operands
+needs to be a Dpkg::Version, the other one can be anything provided that
+its string representation is a version number.
+
+=cut
+
+sub comparison {
+    my ($a, $b, $inverted) = @_;
+    if (not ref($b) or not $b->isa('Dpkg::Version')) {
+        $b = Dpkg::Version->new($b);
+    }
+    ($a, $b) = ($b, $a) if $inverted;
+    my $r = version_compare_part($a->epoch(), $b->epoch());
+    return $r if $r;
+    $r = version_compare_part($a->version(), $b->version());
+    return $r if $r;
+    return version_compare_part($a->revision(), $b->revision());
+}
+
+=item "$v", $v->as_string(), $v->as_string(%options)
+
+Accepts an optional option hash reference, affecting the string conversion.
+
+Options:
+
+=over 8
+
+=item omit_epoch (defaults to 0)
+
+Omit the epoch, if present, in the output string.
+
+=item omit_revision (defaults to 0)
+
+Omit the revision, if present, in the output string.
+
+=back
+
+Returns the string representation of the version number.
+
+=cut
+
+sub as_string {
+    my ($self, %opts) = @_;
+    my $no_epoch = $opts{omit_epoch} || $self->{no_epoch};
+    my $no_revision = $opts{omit_revision} || $self->{no_revision};
+
+    my $str = '';
+    $str .= $self->{epoch} . ':' unless $no_epoch;
+    $str .= $self->{version};
+    $str .= '-' . $self->{revision} unless $no_revision;
+    return $str;
+}
+
+=back
+
+=head1 FUNCTIONS
+
+All the functions are exported by default.
+
+=over 4
+
+=item version_compare($a, $b)
+
+Returns -1 if $a is earlier than $b, 0 if they are equal and 1 if $a
+is later than $b.
+
+If $a or $b are not valid version numbers, it dies with an error.
+
+=cut
+
+sub version_compare($$) {
+    my ($a, $b) = @_;
+    my $va = Dpkg::Version->new($a, check => 1);
+    defined($va) || error(_g('%s is not a valid version'), "$a");
+    my $vb = Dpkg::Version->new($b, check => 1);
+    defined($vb) || error(_g('%s is not a valid version'), "$b");
+    return $va <=> $vb;
+}
+
+=item version_compare_relation($a, $rel, $b)
+
+Returns the result (0 or 1) of the given comparison operation. This
+function is implemented on top of version_compare().
+
+Allowed values for $rel are the exported constants REL_GT, REL_GE,
+REL_EQ, REL_LE, REL_LT. Use version_normalize_relation() if you
+have an input string containing the operator.
+
+=cut
+
+sub version_compare_relation($$$) {
+    my ($a, $op, $b) = @_;
+    my $res = version_compare($a, $b);
+
+    if ($op eq REL_GT) {
+	return $res > 0;
+    } elsif ($op eq REL_GE) {
+	return $res >= 0;
+    } elsif ($op eq REL_EQ) {
+	return $res == 0;
+    } elsif ($op eq REL_LE) {
+	return $res <= 0;
+    } elsif ($op eq REL_LT) {
+	return $res < 0;
+    } else {
+	croak "unsupported relation for version_compare_relation(): '$op'";
+    }
+}
+
+=item my $rel = version_normalize_relation($rel_string)
+
+Returns the normalized constant of the relation $rel (a value
+among REL_GT, REL_GE, REL_EQ, REL_LE and REL_LT). Supported
+relations names in input are: "gt", "ge", "eq", "le", "lt", ">>", ">=",
+"=", "<=", "<<". ">" and "<" are also supported but should not be used as
+they are obsolete aliases of ">=" and "<=".
+
+=cut
+
+sub version_normalize_relation($) {
+    my $op = shift;
+
+    warning('relation %s is deprecated: use %s or %s',
+            $op, "$op$op", "$op=") if ($op eq '>' or $op eq '<');
+
+    if ($op eq '>>' or $op eq 'gt') {
+	return REL_GT;
+    } elsif ($op eq '>=' or $op eq 'ge' or $op eq '>') {
+	return REL_GE;
+    } elsif ($op eq '=' or $op eq 'eq') {
+	return REL_EQ;
+    } elsif ($op eq '<=' or $op eq 'le' or $op eq '<') {
+	return REL_LE;
+    } elsif ($op eq '<<' or $op eq 'lt') {
+	return REL_LT;
+    } else {
+	croak "bad relation '$op'";
+    }
+}
+
+=item version_compare_string($a, $b)
+
+String comparison function used for comparing non-numerical parts of version
+numbers. Returns -1 if $a is earlier than $b, 0 if they are equal and 1 if $a
+is later than $b.
+
+The "~" character always sort lower than anything else. Digits sort lower
+than non-digits. Among remaining characters alphabetic characters (A-Za-z)
+sort lower than the other ones. Within each range, the ASCII decimal value
+of the character is used to sort between characters.
+
+=cut
+
+sub _version_order {
+    my ($x) = @_;
+
+    if ($x eq '~') {
+        return -1;
+    } elsif ($x =~ /^\d$/) {
+        return $x * 1 + 1;
+    } elsif ($x =~ /^[A-Za-z]$/) {
+        return ord($x);
+    } else {
+        return ord($x) + 256;
+    }
+}
+
+sub version_compare_string($$) {
+    my @a = map { _version_order($_) } split(//, shift);
+    my @b = map { _version_order($_) } split(//, shift);
+    while (1) {
+        my ($a, $b) = (shift @a, shift @b);
+        return 0 if not defined($a) and not defined($b);
+        $a ||= 0; # Default order for "no character"
+        $b ||= 0;
+        return 1 if $a > $b;
+        return -1 if $a < $b;
+    }
+}
+
+=item version_compare_part($a, $b)
+
+Compare two corresponding sub-parts of a version number (either upstream
+version or debian revision).
+
+Each parameter is split by version_split_digits() and resulting items
+are compared together. As soon as a difference happens, it returns -1 if
+$a is earlier than $b, 0 if they are equal and 1 if $a is later than $b.
+
+=cut
+
+sub version_compare_part($$) {
+    my @a = version_split_digits(shift);
+    my @b = version_split_digits(shift);
+    while (1) {
+        my ($a, $b) = (shift @a, shift @b);
+        return 0 if not defined($a) and not defined($b);
+        $a ||= 0; # Default value for lack of version
+        $b ||= 0;
+        if ($a =~ /^\d+$/ and $b =~ /^\d+$/) {
+            # Numerical comparison
+            my $cmp = $a <=> $b;
+            return $cmp if $cmp;
+        } else {
+            # String comparison
+            my $cmp = version_compare_string($a, $b);
+            return $cmp if $cmp;
+        }
+    }
+}
+
+=item my @items = version_split_digits($version)
+
+Splits a string in items that are each entirely composed either
+of digits or of non-digits. For instance for "1.024~beta1+svn234" it would
+return ("1", ".", "024", "~beta", "1", "+svn", "234").
+
+=cut
+
+sub version_split_digits($) {
+    return split(/(?<=\d)(?=\D)|(?<=\D)(?=\d)/, $_[0]);
+}
+
+=item my ($ok, $msg) = version_check($version)
+
+=item my $ok = version_check($version)
+
+Checks the validity of $version as a version number. Returns 1 in $ok
+if the version is valid, 0 otherwise. In the latter case, $msg
+contains a description of the problem with the $version scalar.
+
+=cut
+
+sub version_check($) {
+    my $version = shift;
+    my $str;
+    if (defined $version) {
+        $str = "$version";
+        $version = Dpkg::Version->new($str) unless ref($version);
+    }
+    if (not defined($str) or not length($str)) {
+        my $msg = _g('version number cannot be empty');
+        return (0, $msg) if wantarray;
+        return 0;
+    }
+    if ($version->version() =~ m/^[^\d]/) {
+        my $msg = _g('version number does not start with digit');
+        return (0, $msg) if wantarray;
+        return 0;
+    }
+    if ($str =~ m/([^-+:.0-9a-zA-Z~])/o) {
+        my $msg = sprintf(_g("version number contains illegal character `%s'"), $1);
+        return (0, $msg) if wantarray;
+        return 0;
+    }
+    if ($version->epoch() !~ /^\d*$/) {
+        my $msg = sprintf(_g('epoch part of the version number ' .
+                             "is not a number: '%s'"), $version->epoch());
+        return (0, $msg) if wantarray;
+        return 0;
+    }
+    return (1, '') if wantarray;
+    return 1;
+}
+
+=back
+
+=head1 CHANGES
+
+=head2 Version 1.01
+
+New argument: Accept an options argument in $v->as_string().
+
+New method: $v->is_native().
+
+=head1 AUTHOR
+
+Don Armstrong <don@donarmstrong.com>, Colin Watson
+<cjwatson@debian.org> and Raphaël Hertzog <hertzog@debian.org>, based on
+the implementation in F<dpkg/lib/version.c> by Ian Jackson and others.
+
+=cut
+
+1;
diff --git a/third_party/dpkg-dev/scripts/dpkg-shlibdeps.pl b/third_party/dpkg-dev/scripts/dpkg-shlibdeps.pl
new file mode 100755
index 0000000..ea5cb66
--- /dev/null
+++ b/third_party/dpkg-dev/scripts/dpkg-shlibdeps.pl
@@ -0,0 +1,870 @@
+#!/usr/bin/perl
+#
+# dpkg-shlibdeps
+#
+# Copyright © 1996 Ian Jackson
+# Copyright © 2000 Wichert Akkerman
+# Copyright © 2006 Frank Lichtenheld
+# Copyright © 2006-2010,2012-2013 Guillem Jover <guillem@debian.org>
+# Copyright © 2007 Raphaël Hertzog
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+
+use POSIX qw(:errno_h);
+use Cwd qw(realpath);
+use File::Basename qw(dirname);
+
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::ErrorHandling;
+use Dpkg::Util qw(:list);
+use Dpkg::Path qw(relative_to_pkg_root guess_pkg_root_dir
+  check_files_are_the_same get_control_path);
+use Dpkg::Version;
+use Dpkg::Shlibs qw(find_library get_library_paths);
+use Dpkg::Shlibs::Objdump;
+use Dpkg::Shlibs::SymbolFile;
+use Dpkg::Arch qw(get_host_arch);
+use Dpkg::Deps;
+use Dpkg::Control::Info;
+use Dpkg::Control::Fields;
+
+
+use constant {
+  WARN_SYM_NOT_FOUND => 1,
+  WARN_DEP_AVOIDABLE => 2,
+  WARN_NOT_NEEDED => 4,
+};
+
+# By increasing importance
+my @depfields = qw(Suggests Recommends Depends Pre-Depends);
+my $i = 0; my %depstrength = map { $_ => $i++ } @depfields;
+
+textdomain('dpkg-dev');
+
+my $admindir = $Dpkg::ADMINDIR;
+my $shlibsoverride = "$Dpkg::CONFDIR/shlibs.override";
+my $shlibsdefault = "$Dpkg::CONFDIR/shlibs.default";
+my $shlibslocal = 'debian/shlibs.local';
+my $packagetype = 'deb';
+my $dependencyfield = 'Depends';
+my $varlistfile = 'debian/substvars';
+my $varnameprefix = 'shlibs';
+my $ignore_missing_info = 0;
+my $warnings = 3;
+my $debug = 0;
+my @exclude = ();
+my @pkg_dir_to_search = ();
+my $host_arch = get_host_arch();
+
+my (@pkg_shlibs, @pkg_symbols, @pkg_root_dirs);
+if (-d 'debian') {
+  push @pkg_symbols, glob 'debian/*/DEBIAN/symbols';
+  push @pkg_shlibs, glob 'debian/*/DEBIAN/shlibs';
+  my %uniq = map { guess_pkg_root_dir($_) => 1 } (@pkg_symbols, @pkg_shlibs);
+  push @pkg_root_dirs, keys %uniq;
+}
+
+my ($stdout, %exec);
+foreach (@ARGV) {
+  if (m/^-T(.*)$/) {
+	$varlistfile = $1;
+  } elsif (m/^-p(\w[-:0-9A-Za-z]*)$/) {
+	$varnameprefix = $1;
+  } elsif (m/^-L(.*)$/) {
+	$shlibslocal = $1;
+  } elsif (m/^-l(.*)$/) {
+	Dpkg::Shlibs::add_library_dir($1);
+  } elsif (m/^-S(.*)$/) {
+	push @pkg_dir_to_search, $1;
+  } elsif (m/^-O$/) {
+	$stdout = 1;
+  } elsif (m/^-O(.+)$/) {
+	$varlistfile = $1;
+  } elsif (m/^-(\?|-help)$/) {
+	usage(); exit(0);
+  } elsif (m/^--version$/) {
+	version(); exit(0);
+  } elsif (m/^--admindir=(.*)$/) {
+	$admindir = $1;
+	if (not -d $admindir) {
+      error(_g("administrative directory '%s' does not exist"), $admindir);
+	}
+	$ENV{DPKG_ADMINDIR} = $admindir;
+  } elsif (m/^-d(.*)$/) {
+	$dependencyfield = field_capitalize($1);
+	if (not defined $depstrength{$dependencyfield}) {
+      warning(_g("unrecognized dependency field '%s'"), $dependencyfield);
+	}
+  } elsif (m/^-e(.*)$/) {
+	if (exists $exec{$1}) {
+      # Affect the binary to the most important field
+      if ($depstrength{$dependencyfield} > $depstrength{$exec{$1}}) {
+		$exec{$1} = $dependencyfield;
+      }
+	} else {
+      $exec{$1} = $dependencyfield;
+	}
+  } elsif (m/^--ignore-missing-info$/) {
+	$ignore_missing_info = 1;
+  } elsif (m/^--warnings=(\d+)$/) {
+	$warnings = $1;
+  } elsif (m/^-t(.*)$/) {
+	$packagetype = $1;
+  } elsif (m/^-v$/) {
+	$debug++;
+  } elsif (m/^-x(.*)$/) {
+	push @exclude, $1;
+  } elsif (m/^-/) {
+	usageerr(_g("unknown option \`%s'"), $_);
+  } else {
+	if (exists $exec{$_}) {
+      # Affect the binary to the most important field
+      if ($depstrength{$dependencyfield} > $depstrength{$exec{$_}}) {
+		$exec{$_} = $dependencyfield;
+      }
+	} else {
+      $exec{$_} = $dependencyfield;
+	}
+  }
+}
+usageerr(_g('need at least one executable')) unless scalar keys %exec;
+
+my $control = Dpkg::Control::Info->new();
+my $fields = $control->get_source();
+my $bd_value = deps_concat($fields->{'Build-Depends'}, $fields->{'Build-Depends-Arch'});
+my $build_deps = deps_parse($bd_value, build_dep => 1, reduce_restrictions => 1);
+error(_g('error occurred while parsing %s'), 'Build-Depends/Build-Depends-Arch')
+  unless defined $build_deps;
+
+my %dependencies;
+
+# Statictics on soname seen in the whole run (with multiple analysis of
+# binaries)
+my %global_soname_notfound;
+my %global_soname_used;
+my %global_soname_needed;
+
+# Symfile and objdump caches
+my %symfile_cache;
+my %objdump_cache;
+my %symfile_has_soname_cache;
+
+# Used to count errors due to missing libraries
+my $error_count = 0;
+
+my $cur_field;
+foreach my $file (keys %exec) {
+  $cur_field = $exec{$file};
+  print ">> Scanning $file (for $cur_field field)\n" if $debug;
+
+  my $obj = Dpkg::Shlibs::Objdump::Object->new($file);
+  my @sonames = $obj->get_needed_libraries;
+
+  # Load symbols files for all needed libraries (identified by SONAME)
+  my %libfiles;
+  my %altlibfiles;
+  my %soname_notfound;
+  my %alt_soname;
+  foreach my $soname (@sonames) {
+	my $lib = my_find_library($soname, $obj->{RPATH}, $obj->{format}, $file);
+	unless (defined $lib) {
+      $soname_notfound{$soname} = 1;
+      $global_soname_notfound{$soname} = 1;
+      my $msg = _g("couldn't find library %s needed by %s (ELF " .
+                   "format: '%s'; RPATH: '%s')");
+      if (scalar(split_soname($soname))) {
+		errormsg($msg, $soname, $file, $obj->{format}, join(':', @{$obj->{RPATH}}));
+		$error_count++;
+      } else {
+		warning($msg, $soname, $file, $obj->{format}, join(':', @{$obj->{RPATH}}));
+      }
+      next;
+	}
+	$libfiles{$lib} = $soname;
+	my $reallib = realpath($lib);
+	if ($reallib ne $lib) {
+      $altlibfiles{$reallib} = $soname;
+	}
+	print "Library $soname found in $lib\n" if $debug;
+  }
+  my $file2pkg = find_packages(keys %libfiles, keys %altlibfiles);
+  my $symfile = Dpkg::Shlibs::SymbolFile->new();
+  my $dumplibs_wo_symfile = Dpkg::Shlibs::Objdump->new();
+  my @soname_wo_symfile;
+  foreach my $lib (keys %libfiles) {
+	my $soname = $libfiles{$lib};
+
+	if (none { $_ ne '' } @{$file2pkg->{$lib}}) {
+      # The path of the library as calculated is not the
+      # official path of a packaged file, try to fallback on
+      # on the realpath() first, maybe this one is part of a package
+      my $reallib = realpath($lib);
+      if (exists $file2pkg->{$reallib}) {
+		$file2pkg->{$lib} = $file2pkg->{$reallib};
+      }
+	}
+	if (none { $_ ne '' } @{$file2pkg->{$lib}}) {
+      # If the library is really not available in an installed package,
+      # it's because it's in the process of being built
+      # Empty package name will lead to consideration of symbols
+      # file from the package being built only
+      $file2pkg->{$lib} = [''];
+      print "No associated package found for $lib\n" if $debug;
+	}
+
+	# Load symbols/shlibs files from packages providing libraries
+	foreach my $pkg (@{$file2pkg->{$lib}}) {
+      my $symfile_path;
+      my $haslocaldep = 0;
+      if (-e $shlibslocal and
+          defined(extract_from_shlibs($soname, $shlibslocal)))
+      {
+        $haslocaldep = 1;
+      }
+      if ($packagetype eq 'deb' and not $haslocaldep) {
+		# Use fine-grained dependencies only on real deb
+        # and only if the dependency is not provided by shlibs.local
+		$symfile_path = find_symbols_file($pkg, $soname, $lib);
+      }
+      if (defined($symfile_path)) {
+        # Load symbol information
+        print "Using symbols file $symfile_path for $soname\n" if $debug;
+        unless (exists $symfile_cache{$symfile_path}) {
+          $symfile_cache{$symfile_path} =
+            Dpkg::Shlibs::SymbolFile->new(file => $symfile_path);
+        }
+        $symfile->merge_object_from_symfile($symfile_cache{$symfile_path}, $soname);
+      }
+      if (defined($symfile_path) && $symfile->has_object($soname)) {
+		# Initialize dependencies with the smallest minimal version
+        # of all symbols (unversioned dependency is not ok as the
+        # library might not have always been available in the
+        # package and we really need it)
+		my $dep = $symfile->get_dependency($soname);
+		my $minver = $symfile->get_smallest_version($soname) || '';
+		foreach my $subdep (split /\s*,\s*/, $dep) {
+          if (not exists $dependencies{$cur_field}{$subdep}) {
+			$dependencies{$cur_field}{$subdep} = Dpkg::Version->new($minver);
+            print " Initialize dependency ($subdep) with minimal " .
+              "version ($minver)\n" if $debug > 1;
+          }
+		}
+      } else {
+		# No symbol file found, fall back to standard shlibs
+        print "Using shlibs+objdump for $soname (file $lib)\n" if $debug;
+        unless (exists $objdump_cache{$lib}) {
+          $objdump_cache{$lib} = Dpkg::Shlibs::Objdump::Object->new($lib);
+        }
+        my $libobj = $objdump_cache{$lib};
+        my $id = $dumplibs_wo_symfile->add_object($libobj);
+		if (($id ne $soname) and ($id ne $lib)) {
+          warning(_g('%s has an unexpected SONAME (%s)'), $lib, $id);
+          $alt_soname{$id} = $soname;
+		}
+		push @soname_wo_symfile, $soname;
+		# Only try to generate a dependency for libraries with a SONAME
+		if ($libobj->is_public_library() and not
+		    add_shlibs_dep($soname, $pkg, $lib)) {
+          # This failure is fairly new, try to be kind by
+          # ignoring as many cases that can be safely ignored
+          my $ignore = 0;
+          # 1/ when the lib and the binary are in the same
+          # package
+          my $root_file = guess_pkg_root_dir($file);
+          my $root_lib = guess_pkg_root_dir($lib);
+          $ignore++ if defined $root_file and defined $root_lib
+			and check_files_are_the_same($root_file, $root_lib);
+          # 2/ when the lib is not versioned and can't be
+          # handled by shlibs
+          $ignore++ unless scalar(split_soname($soname));
+          # 3/ when we have been asked to do so
+          $ignore++ if $ignore_missing_info;
+          error(_g('no dependency information found for %s ' .
+                   '(used by %s)'), $lib, $file)
+            unless $ignore;
+		}
+      }
+	}
+  }
+
+  # Scan all undefined symbols of the binary and resolve to a
+  # dependency
+  my %soname_used;
+  foreach (@sonames) {
+    # Initialize statistics
+    $soname_used{$_} = 0;
+    $global_soname_used{$_} = 0 unless exists $global_soname_used{$_};
+    if (exists $global_soname_needed{$_}) {
+      push @{$global_soname_needed{$_}}, $file;
+    } else {
+      $global_soname_needed{$_} = [ $file ];
+    }
+  }
+  my $nb_warnings = 0;
+  my $nb_skipped_warnings = 0;
+  # Disable warnings about missing symbols when we have not been able to
+  # find all libs
+  my $disable_warnings = scalar(keys(%soname_notfound));
+  my $in_public_dir = 1;
+  if (my $relname = relative_to_pkg_root($file)) {
+    my $parent_dir = '/' . dirname($relname);
+    $in_public_dir = any { $parent_dir eq $_ } get_library_paths();
+  } else {
+    warning(_g('binaries to analyze should already be ' .
+               "installed in their package's directory"));
+  }
+  print "Analyzing all undefined symbols\n" if $debug > 1;
+  foreach my $sym ($obj->get_undefined_dynamic_symbols()) {
+	my $name = $sym->{name};
+	if ($sym->{version}) {
+      $name .= '@' . "$sym->{version}";
+	} else {
+      $name .= '@' . 'Base';
+	}
+    print " Looking up symbol $name\n" if $debug > 1;
+	my %symdep = $symfile->lookup_symbol($name, \@sonames);
+	if (keys %symdep) {
+      my $depends = $symfile->get_dependency($symdep{soname},
+                                             $symdep{symbol}{dep_id});
+      print " Found in symbols file of $symdep{soname} (minver: " .
+        "$symdep{symbol}{minver}, dep: $depends)\n" if $debug > 1;
+      $soname_used{$symdep{soname}}++;
+      $global_soname_used{$symdep{soname}}++;
+      if (exists $alt_soname{$symdep{soname}}) {
+        # Also count usage on alternate soname
+        $soname_used{$alt_soname{$symdep{soname}}}++;
+        $global_soname_used{$alt_soname{$symdep{soname}}}++;
+      }
+      update_dependency_version($depends, $symdep{symbol}{minver});
+	} else {
+      my $syminfo = $dumplibs_wo_symfile->locate_symbol($name);
+      if (not defined($syminfo)) {
+        print " Not found\n" if $debug > 1;
+        next unless ($warnings & WARN_SYM_NOT_FOUND);
+		next if $disable_warnings;
+		# Complain about missing symbols only for executables
+		# and public libraries
+		if ($obj->is_executable() or $obj->is_public_library()) {
+          my $print_name = $name;
+          # Drop the default suffix for readability
+          $print_name =~ s/\@Base$//;
+          unless ($sym->{weak}) {
+			if ($debug or ($in_public_dir and $nb_warnings < 10)
+                or (not $in_public_dir and $nb_warnings < 1))
+            {
+              if ($in_public_dir) {
+                warning(_g('symbol %s used by %s found in none of the ' .
+				           'libraries'), $print_name, $file);
+              } else {
+                warning(_g('%s contains an unresolvable reference to ' .
+                           "symbol %s: it's probably a plugin"),
+                        $file, $print_name);
+              }
+              $nb_warnings++;
+			} else {
+              $nb_skipped_warnings++;
+			}
+          }
+		}
+      } else {
+        print " Found in $syminfo->{soname} ($syminfo->{objid})\n" if $debug > 1;
+		if (exists $alt_soname{$syminfo->{soname}}) {
+          # Also count usage on alternate soname
+          $soname_used{$alt_soname{$syminfo->{soname}}}++;
+          $global_soname_used{$alt_soname{$syminfo->{soname}}}++;
+		}
+		$soname_used{$syminfo->{soname}}++;
+		$global_soname_used{$syminfo->{soname}}++;
+      }
+	}
+  }
+  warning(P_('%d similar warning has been skipped (use -v to see it)',
+             '%d other similar warnings have been skipped (use -v to see ' .
+             'them all)', $nb_skipped_warnings), $nb_skipped_warnings)
+    if $nb_skipped_warnings;
+  foreach my $soname (@sonames) {
+	# Adjust minimal version of dependencies with information
+	# extracted from build-dependencies
+	my $dev_pkg = $symfile->get_field($soname, 'Build-Depends-Package');
+	if (defined $dev_pkg) {
+      print "Updating dependencies of $soname with build-dependencies\n" if $debug;
+      my $minver = get_min_version_from_deps($build_deps, $dev_pkg);
+      if (defined $minver) {
+		foreach my $dep ($symfile->get_dependencies($soname)) {
+          update_dependency_version($dep, $minver, 1);
+          print " Minimal version of $dep updated with $minver\n" if $debug;
+		}
+      } else {
+        print " No minimal version found in $dev_pkg build-dependency\n" if $debug;
+      }
+	}
+
+	# Warn about un-NEEDED libraries
+	unless ($soname_notfound{$soname} or $soname_used{$soname}) {
+      # Ignore warning for libm.so.6 if also linked against libstdc++
+      next if ($soname =~ /^libm\.so\.\d+$/ and
+               any { m/^libstdc\+\+\.so\.\d+/ } @sonames);
+      next unless ($warnings & WARN_NOT_NEEDED);
+      warning(_g('%s should not be linked against %s (it uses none of ' .
+                 "the library's symbols)"), $file, $soname);
+	}
+  }
+}
+
+# Warn of unneeded libraries at the "package" level (i.e. over all
+# binaries that we have inspected)
+foreach my $soname (keys %global_soname_needed) {
+  unless ($global_soname_notfound{$soname} or $global_soname_used{$soname}) {
+    next if ($soname =~ /^libm\.so\.\d+$/ and
+             any { m/^libstdc\+\+\.so\.\d+/ } keys %global_soname_needed);
+    next unless ($warnings & WARN_DEP_AVOIDABLE);
+    warning(P_('package could avoid a useless dependency if %s was not ' .
+               "linked against %s (it uses none of the library's symbols)",
+               'package could avoid a useless dependency if %s were not ' .
+               "linked against %s (they use none of the library's symbols)",
+               scalar @{$global_soname_needed{$soname}}),
+            join(' ', @{$global_soname_needed{$soname}}), $soname);
+  }
+}
+
+# Quit now if any missing libraries
+if ($error_count >= 1) {
+  my $note = _g('Note: libraries are not searched in other binary packages ' .
+                "that do not have any shlibs or symbols file.\nTo help dpkg-shlibdeps " .
+                'find private libraries, you might need to use -l.');
+  error(P_('cannot continue due to the error above',
+           'cannot continue due to the errors listed above',
+           $error_count) . "\n" . $note);
+}
+
+# Open substvars file
+my $fh;
+if ($stdout) {
+  $fh = \*STDOUT;
+} else {
+  open(my $new_fh, '>', "$varlistfile.new")
+    or syserr(_g("open new substvars file \`%s'"), "$varlistfile.new");
+  if (-e $varlistfile) {
+	open(my $old_fh, '<', $varlistfile)
+      or syserr(_g("open old varlist file \`%s' for reading"), $varlistfile);
+	while (my $entry = <$old_fh>) {
+      next if $entry =~ m/^\Q$varnameprefix\E:/;
+      print { $new_fh } $entry
+        or syserr(_g("copy old entry to new varlist file \`%s'"),
+                  "$varlistfile.new");
+	}
+	close($old_fh);
+  }
+  $fh = $new_fh;
+}
+
+# Write out the shlibs substvars
+my %depseen;
+
+sub filter_deps {
+  my ($dep, $field) = @_;
+  # Skip dependencies on excluded packages
+  foreach my $exc (@exclude) {
+	return 0 if $dep =~ /^\s*\Q$exc\E\b/;
+  }
+  # Don't include dependencies if they are already
+  # mentionned in a higher priority field
+  if (not exists($depseen{$dep})) {
+	$depseen{$dep} = $dependencies{$field}{$dep};
+	return 1;
+  } else {
+	# Since dependencies can be versionned, we have to
+	# verify if the dependency is stronger than the
+	# previously seen one
+	my $stronger;
+	if ($depseen{$dep} eq $dependencies{$field}{$dep}) {
+      # If both versions are the same (possibly unversionned)
+      $stronger = 0;
+	} elsif ($dependencies{$field}{$dep} eq '') {
+      $stronger = 0; # If the dep is unversionned
+	} elsif ($depseen{$dep} eq '') {
+      $stronger = 1; # If the dep seen is unversionned
+	} elsif (version_compare_relation($depseen{$dep}, REL_GT,
+                                      $dependencies{$field}{$dep})) {
+      # The version of the dep seen is stronger...
+      $stronger = 0;
+	} else {
+      $stronger = 1;
+	}
+	$depseen{$dep} = $dependencies{$field}{$dep} if $stronger;
+	return $stronger;
+  }
+}
+
+foreach my $field (reverse @depfields) {
+  my $dep = '';
+  if (exists $dependencies{$field} and scalar keys %{$dependencies{$field}}) {
+	$dep = join ', ',
+      map {
+		# Translate dependency templates into real dependencies
+		if ($dependencies{$field}{$_}) {
+          s/#MINVER#/(>= $dependencies{$field}{$_})/g;
+		} else {
+          s/#MINVER#//g;
+		}
+		s/\s+/ /g;
+		$_;
+    } grep { filter_deps($_, $field) }
+    keys %{$dependencies{$field}};
+  }
+  if ($dep) {
+    my $obj = deps_parse($dep);
+    error(_g('invalid dependency got generated: %s'), $dep) unless defined $obj;
+    $obj->sort();
+	print { $fh } "$varnameprefix:$field=$obj\n";
+  }
+}
+
+# Replace old file by new one
+if (!$stdout) {
+  close($fh) or syserr(_g('cannot close %s'), "$varlistfile.new");
+  rename("$varlistfile.new",$varlistfile)
+    or syserr(_g("install new varlist file \`%s'"), $varlistfile);
+}
+
+##
+## Functions
+##
+
+sub version {
+  printf _g("Debian %s version %s.\n"), $Dpkg::PROGNAME, $Dpkg::PROGVERSION;
+
+  printf _g('
+            This is free software; see the GNU General Public License version 2 or
+            later for copying conditions. There is NO warranty.
+            ');
+}
+
+sub usage {
+  printf _g(
+    'Usage: %s [<option>...] <executable>|-e<executable> [<option>...]')
+    . "\n\n" . _g(
+    "Positional options (order is significant):
+    <executable>             include dependencies for <executable>,
+    -e<executable>           (use -e if <executable> starts with '-')
+    -d<dependency-field>     next executable(s) set shlibs:<dependency-field>.")
+    . "\n\n" . _g(
+    "Options:
+    -l<library-dir>          add directory to private shared library search list.
+    -p<varname-prefix>       set <varname-prefix>:* instead of shlibs:*.
+    -O[<file>]               write variable settings to stdout (or <file>).
+    -L<local-shlibs-file>    shlibs override file, not debian/shlibs.local.
+    -T<substvars-file>       update variables here, not debian/substvars.
+    -t<type>                 set package type (default is deb).
+    -x<package>              exclude package from the generated dependencies.
+    -S<package-build-dir>    search needed libraries in the given
+    package build directory first.
+    -v                       enable verbose mode (can be used multiple times).
+    --ignore-missing-info    don't fail if dependency information can't be found.
+    --warnings=<value>       define set of active warnings (see manual page).
+    --admindir=<directory>   change the administrative directory.
+    -?, --help               show this help message.
+    --version            show the version.")
+    . "\n\n" . _g(
+    'Dependency fields recognized are:
+    %s
+    '), $Dpkg::PROGNAME, join('/', @depfields);
+}
+
+sub get_min_version_from_deps {
+  my ($dep, $pkg) = @_;
+  if ($dep->isa('Dpkg::Deps::Simple')) {
+	if (($dep->{package} eq $pkg) &&
+	    defined($dep->{relation}) &&
+	    (($dep->{relation} eq REL_GE) ||
+	     ($dep->{relation} eq REL_GT)))
+	{
+      return $dep->{version};
+	}
+	return;
+  } else {
+	my $res;
+	foreach my $subdep ($dep->get_deps()) {
+      my $minver = get_min_version_from_deps($subdep, $pkg);
+      next if not defined $minver;
+      if (defined $res) {
+		if (version_compare_relation($minver, REL_GT, $res)) {
+          $res = $minver;
+		}
+      } else {
+		$res = $minver;
+      }
+	}
+	return $res;
+  }
+}
+
+sub update_dependency_version {
+  my ($dep, $minver, $existing_only) = @_;
+  return if not defined($minver);
+  $minver = Dpkg::Version->new($minver);
+  foreach my $subdep (split /\s*,\s*/, $dep) {
+	if (exists $dependencies{$cur_field}{$subdep} and
+	    defined($dependencies{$cur_field}{$subdep}))
+	{
+      if ($dependencies{$cur_field}{$subdep} eq '' or
+          version_compare_relation($minver, REL_GT,
+                                   $dependencies{$cur_field}{$subdep}))
+      {
+		$dependencies{$cur_field}{$subdep} = $minver;
+      }
+	} elsif (!$existing_only) {
+      $dependencies{$cur_field}{$subdep} = $minver;
+	}
+  }
+}
+
+sub add_shlibs_dep {
+  my ($soname, $pkg, $libfile) = @_;
+  my @shlibs = ($shlibslocal, $shlibsoverride);
+  if ($pkg eq '') {
+	# If the file is not packaged, try to find out the shlibs file in
+	# the package being built where the lib has been found
+	my $pkg_root = guess_pkg_root_dir($libfile);
+	if (defined $pkg_root) {
+      push @shlibs, "$pkg_root/DEBIAN/shlibs";
+	}
+	# Fallback to other shlibs files but it shouldn't be necessary
+	push @shlibs, @pkg_shlibs;
+  } else {
+	my $control_file = get_control_path($pkg, 'shlibs');
+	push @shlibs, $control_file if defined $control_file;
+  }
+  push @shlibs, $shlibsdefault;
+  print " Looking up shlibs dependency of $soname provided by '$pkg'\n" if $debug;
+  foreach my $file (@shlibs) {
+	next if not -e $file;
+	my $dep = extract_from_shlibs($soname, $file);
+	if (defined($dep)) {
+      print " Found $dep in $file\n" if $debug;
+      foreach (split(/,\s*/, $dep)) {
+		# Note: the value is empty for shlibs based dependency
+		# symbol based dependency will put a valid version as value
+		$dependencies{$cur_field}{$_} = Dpkg::Version->new('');
+      }
+      return 1;
+	}
+  }
+  print " Found nothing\n" if $debug;
+  return 0;
+}
+
+sub split_soname {
+  my $soname = shift;
+  if ($soname =~ /^(.*)\.so\.(.*)$/) {
+	return wantarray ? ($1, $2) : 1;
+  } elsif ($soname =~ /^(.*)-(\d.*)\.so$/) {
+	return wantarray ? ($1, $2) : 1;
+  } else {
+	return wantarray ? () : 0;
+  }
+}
+
+sub extract_from_shlibs {
+  my ($soname, $shlibfile) = @_;
+  # Split soname in name/version
+  my ($libname, $libversion) = split_soname($soname);
+  unless (defined $libname) {
+	warning(_g("can't extract name and version from library name '%s'"),
+	        $soname);
+	return;
+  }
+  # Open shlibs file
+  open(my $shlibs_fh, '<', $shlibfile)
+    or syserr(_g("unable to open shared libs info file \`%s'"), $shlibfile);
+  my $dep;
+  while (<$shlibs_fh>) {
+	s/\s*\n$//;
+	next if m/^\#/;
+	if (!m/^\s*(?:(\S+):\s+)?(\S+)\s+(\S+)(?:\s+(\S.*\S))?\s*$/) {
+      warning(_g("shared libs info file \`%s' line %d: bad line \`%s'"),
+              $shlibfile, $., $_);
+      next;
+	}
+	my $depread = defined($4) ? $4 : '';
+	if (($libname eq $2) && ($libversion eq $3)) {
+      # Define dep and end here if the package type explicitly
+      # matches. Otherwise if the packagetype is not specified, use
+      # the dep only as a default that can be overriden by a later
+      # line
+      if (defined($1)) {
+		if ($1 eq $packagetype) {
+          $dep = $depread;
+          last;
+		}
+      } else {
+		$dep //= $depread;
+      }
+	}
+  }
+  close($shlibs_fh);
+  return $dep;
+}
+
+sub find_symbols_file {
+  my ($pkg, $soname, $libfile) = @_;
+  my @files;
+  if ($pkg eq '') {
+	# If the file is not packaged, try to find out the symbols file in
+	# the package being built where the lib has been found
+	my $pkg_root = guess_pkg_root_dir($libfile);
+	if (defined $pkg_root) {
+      push @files, "$pkg_root/DEBIAN/symbols";
+	}
+	# Fallback to other symbols files but it shouldn't be necessary
+	push @files, @pkg_symbols;
+  } else {
+	push @files, "$Dpkg::CONFDIR/symbols/$pkg.symbols.$host_arch",
+      "$Dpkg::CONFDIR/symbols/$pkg.symbols";
+	my $control_file = get_control_path($pkg, 'symbols');
+	push @files, $control_file if defined $control_file;
+  }
+
+  foreach my $file (@files) {
+	if (-e $file and symfile_has_soname($file, $soname)) {
+      return $file;
+	}
+  }
+  return;
+}
+
+sub symfile_has_soname {
+  my ($file, $soname) = @_;
+
+  if (exists $symfile_has_soname_cache{$file}{$soname}) {
+    return $symfile_has_soname_cache{$file}{$soname};
+  }
+
+  open(my $symfile_fh, '<', $file)
+    or syserr(_g('cannot open file %s'), $file);
+  my $result = 0;
+  while (<$symfile_fh>) {
+	if (/^\Q$soname\E /) {
+      $result = 1;
+      last;
+	}
+  }
+  close($symfile_fh);
+  $symfile_has_soname_cache{$file}{$soname} = $result;
+  return $result;
+}
+
+# find_library ($soname, \@rpath, $format)
+sub my_find_library {
+  my ($lib, $rpath, $format, $execfile) = @_;
+  my $file;
+
+  # Create real RPATH in case $ORIGIN is used
+  # Note: ld.so also supports $PLATFORM and $LIB but they are
+  # used in real case (yet)
+  my $libdir = relative_to_pkg_root($execfile);
+  my $origin;
+  if (defined $libdir) {
+	$origin = "/$libdir";
+	$origin =~ s{/+[^/]*$}{};
+  }
+  my @RPATH = ();
+  foreach my $path (@{$rpath}) {
+	if ($path =~ /\$ORIGIN|\$\{ORIGIN\}/) {
+      if (defined $origin) {
+		$path =~ s/\$ORIGIN/$origin/g;
+		$path =~ s/\$\{ORIGIN\}/$origin/g;
+      } else {
+		warning(_g('$ORIGIN is used in RPATH of %s and the corresponding ' .
+                   'directory could not be identified due to lack of DEBIAN ' .
+                   "sub-directory in the root of package's build tree"), $execfile);
+      }
+	}
+	push @RPATH, $path;
+  }
+
+  # Look into the packages we're currently building in the following
+  # order:
+  # - package build tree of the binary which is analyzed
+  # - package build tree given on the command line (option -S)
+  # - other package build trees that contain either a shlibs or a
+  # symbols file
+  my @builddirs;
+  my $pkg_root = guess_pkg_root_dir($execfile);
+  push @builddirs, $pkg_root if defined $pkg_root;
+  push @builddirs, @pkg_dir_to_search;
+  push @builddirs, @pkg_root_dirs;
+  my %dir_checked;
+  foreach my $builddir (@builddirs) {
+	next if defined($dir_checked{$builddir});
+	$file = find_library($lib, \@RPATH, $format, $builddir);
+	return $file if defined($file);
+	$dir_checked{$builddir} = 1;
+  }
+
+  # Fallback in the root directory if we have not found what we were
+  # looking for in the packages
+  $file = find_library($lib, \@RPATH, $format, '');
+  return $file if defined($file);
+
+  return;
+}
+
+my %cached_pkgmatch = ();
+
+sub find_packages {
+  my @files;
+  my $pkgmatch = {};
+
+  foreach (@_) {
+	if (exists $cached_pkgmatch{$_}) {
+      $pkgmatch->{$_} = $cached_pkgmatch{$_};
+	} else {
+      push @files, $_;
+      $cached_pkgmatch{$_} = ['']; # placeholder to cache misses too.
+      $pkgmatch->{$_} = [''];        # might be replaced later on
+	}
+  }
+  return $pkgmatch unless scalar(@files);
+
+  my $pid = open(my $dpkg_fh, '-|');
+  syserr(_g('cannot fork for %s'), 'dpkg --search') unless defined($pid);
+  if (!$pid) {
+	# Child process running dpkg --search and discarding errors
+	close STDERR;
+	open STDERR, '>', '/dev/null'
+      or syserr(_g('cannot open file %s'), '/dev/null');
+	$ENV{LC_ALL} = 'C';
+	exec('dpkg', '--search', '--', @files)
+      or syserr(_g('unable to execute %s'), 'dpkg');
+  }
+  while (defined($_ = <$dpkg_fh>)) {
+	chomp($_);
+	if (m/^local diversion |^diversion by/) {
+      warning(_g('diversions involved - output may be incorrect'));
+      print { *STDERR } " $_\n"
+		or syserr(_g('write diversion info to stderr'));
+	} elsif (m/^([-a-z0-9+.:, ]+): (\/.*)$/) {
+      $cached_pkgmatch{$2} = $pkgmatch->{$2} = [ split(/, /, $1) ];
+	} else {
+      warning(_g("unknown output from dpkg --search: '%s'"), $_);
+	}
+  }
+  close($dpkg_fh);
+  return $pkgmatch;
+}
diff --git a/third_party/dpkg-dev/triplettable b/third_party/dpkg-dev/triplettable
new file mode 100644
index 0000000..a2c683f2
--- /dev/null
+++ b/third_party/dpkg-dev/triplettable
@@ -0,0 +1,31 @@
+# Bidirectional mapping between a Debian triplet and a Debian arch.
+#
+# Supported variables: <cpu>
+#
+# <Debian triplet>	<Debian arch>
+uclibceabi-linux-arm	uclibc-linux-armel
+uclibc-linux-<cpu>	uclibc-linux-<cpu>
+musleabihf-linux-arm	musl-linux-armhf
+musl-linux-<cpu>	musl-linux-<cpu>
+gnueabihf-linux-arm	armhf
+gnueabi-linux-arm	armel
+gnuabin32-linux-mips64el	mipsn32el
+gnuabin32-linux-mips64	mipsn32
+gnuabi64-linux-mips64el	mips64el
+gnuabi64-linux-mips64	mips64
+gnuspe-linux-powerpc	powerpcspe
+gnux32-linux-amd64	x32
+gnulp-linux-i386	lpia
+gnu-linux-<cpu>		<cpu>
+gnu-kfreebsd-<cpu>	kfreebsd-<cpu>
+gnu-knetbsd-<cpu>	knetbsd-<cpu>
+gnu-kopensolaris-<cpu>	kopensolaris-<cpu>
+gnu-hurd-<cpu>		hurd-<cpu>
+bsd-freebsd-<cpu>	freebsd-<cpu>
+bsd-openbsd-<cpu>	openbsd-<cpu>
+bsd-netbsd-<cpu>	netbsd-<cpu>
+bsd-darwin-<cpu>	darwin-<cpu>
+sysv-solaris-<cpu>	solaris-<cpu>
+uclibceabi-uclinux-arm	uclinux-armel
+uclibc-uclinux-<cpu>	uclinux-<cpu>
+tos-mint-m68k		mint-m68k
diff --git a/tools/checklicenses/checklicenses.py b/tools/checklicenses/checklicenses.py
index 3d27b0f2..cfb09bc7 100755
--- a/tools/checklicenses/checklicenses.py
+++ b/tools/checklicenses/checklicenses.py
@@ -231,6 +231,11 @@
         'UNKNOWN',
     ],
 
+    # https://bugs.chromium.org/p/chromium/issues/detail?id=655755
+    'third_party/dpkg-dev': [
+        'GPL (v2 or later)',
+    ],
+
     'third_party/devscripts': [
         'GPL (v2 or later)',
     ],
diff --git a/tools/metrics/histograms/extract_histograms.py b/tools/metrics/histograms/extract_histograms.py
index b8e49ad..c8da0ccb 100644
--- a/tools/metrics/histograms/extract_histograms.py
+++ b/tools/metrics/histograms/extract_histograms.py
@@ -63,6 +63,9 @@
 
 MAX_HISTOGRAM_SUFFIX_DEPENDENCY_DEPTH = 5
 
+DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON = (
+    'Base histogram. Use suffixes of this histogram instead.')
+
 
 class Error(Exception):
   pass
@@ -230,6 +233,14 @@
   return owners
 
 
+def _ProcessBaseHistogramAttribute(node, histogram_entry):
+  if node.hasAttribute('base'):
+    is_base = node.getAttribute('base').lower() == 'true'
+    histogram_entry['base'] = is_base
+    if is_base and 'obsolete' not in histogram_entry:
+      histogram_entry['obsolete'] = DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON
+
+
 def _ExtractHistogramsFromXmlTree(tree, enums):
   """Extract all <histogram> nodes in the tree into a dictionary."""
 
@@ -288,6 +299,8 @@
       else:
         histogram_entry['enum'] = enums[enum_name]
 
+    _ProcessBaseHistogramAttribute(histogram, histogram_entry)
+
   return histograms, have_errors
 
 
@@ -398,8 +411,17 @@
           new_histogram_name = _ExpandHistogramNameWithSuffixes(
               suffix_name, histogram_name, histogram_suffixes)
           if new_histogram_name != histogram_name:
-            histograms[new_histogram_name] = copy.deepcopy(
-                histograms[histogram_name])
+            new_histogram = copy.deepcopy(histograms[histogram_name])
+            # Do not copy forward base histogram state to suffixed
+            # histograms. Any suffixed histograms that wish to remain base
+            # histograms must explicitly re-declare themselves as base
+            # histograms.
+            if new_histogram.get('base', False):
+              del new_histogram['base']
+              if (new_histogram.get('obsolete', '') ==
+                  DEFAULT_BASE_HISTOGRAM_OBSOLETE_REASON):
+                del new_histogram['obsolete']
+            histograms[new_histogram_name] = new_histogram
 
           suffix_label = suffix_labels.get(suffix_name, '')
 
@@ -436,6 +458,8 @@
           if obsolete_reason:
             histograms[new_histogram_name]['obsolete'] = obsolete_reason
 
+          _ProcessBaseHistogramAttribute(suffix, histograms[new_histogram_name])
+
         except Error:
           have_errors = True
 
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index cf35fd4..8428fb9f 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -6827,6 +6827,60 @@
   </summary>
 </histogram>
 
+<histogram name="Conflicts.ConfirmedBadModules" units="modules">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The number of confirmed bad modules found during module enumeration.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.EnumerateLoadedModules" units="ms">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The length of time it takes to enumerate the loaded modules in the browser
+    process.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.EnumerateShellExtensions" units="ms">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The length of time it takes to enumerate the shell extensions.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.EnumerateWinsockModules" units="ms">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The length of time it takes to enumerate the Winsock LSP modules.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.EnumerationInspectionTime" units="ms">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The cumulative length of time it takes to inspect all modules on disk,
+    extracting their certificates and version information. This work is actually
+    spread out over a much longer period of time so as not to impact the user.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.EnumerationTotalTime" units="ms">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The cumulative length of time it takes to enumerate and inspect all modules.
+    This work is actually spread out over a much longer period of time so as not
+    to impact the user.
+  </summary>
+</histogram>
+
+<histogram name="Conflicts.SuspectedBadModules" units="modules">
+  <owner>chrisha@chromium.org</owner>
+  <summary>
+    The number of suspected bad modules found during module enumeration.
+  </summary>
+</histogram>
+
 <histogram name="ConnectivityDiagnostics.ChromeOsSignalStrength" units="%">
   <owner>ebeach@google.com</owner>
   <summary>
@@ -21427,6 +21481,21 @@
 
 <histogram name="interstitial.ssl.clockstate.network2"
     enum="NetworkClockStates">
+  <obsolete>
+    Deprecated October 2016. Due to a bug, data in this histogram is mislabelled
+    and should be disregarded. Replaced with
+    interstitial.ssl.clockstate.network3.
+  </obsolete>
+  <owner>estark@chromium.org</owner>
+  <owner>mab@chromium.org</owner>
+  <summary>
+    State of the system clock, relative to network time, when an SSL
+    CERT_INVALID_DATE error is seen.
+  </summary>
+</histogram>
+
+<histogram name="interstitial.ssl.clockstate.network3"
+    enum="NetworkClockStates">
   <owner>estark@chromium.org</owner>
   <owner>mab@chromium.org</owner>
   <summary>
@@ -40306,7 +40375,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.Background" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.Background" units="ms">
   <owner>bmcquade@chromium.org</owner>
   <owner>csharrison@chromium.org</owner>
   <summary>
@@ -40316,7 +40385,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.ClientRedirect" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.ClientRedirect" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40325,7 +40394,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.Close" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.Close" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40334,7 +40403,8 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.ForwardBackNavigation" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.ForwardBackNavigation"
+    units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40343,7 +40413,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.NewNavigation" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.NewNavigation" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40352,7 +40422,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.Other" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.Other" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40361,7 +40431,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.Reload" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.Reload" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -40370,7 +40440,7 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.AbortTiming.Stop" units="ms">
+<histogram base="true" name="PageLoad.AbortTiming.Stop" units="ms">
   <owner>csharrison@chromium.org</owner>
   <summary>
     This metric is still experimental and not yet ready to be relied upon.
@@ -73726,7 +73796,6 @@
   <int value="140" label="ARH_CREATED_STREAM_WITHOUT_AUTHORIZATION"/>
   <int value="141" label="MDDH_INVALID_DEVICE_TYPE_REQUEST"/>
   <int value="142" label="MDDH_UNAUTHORIZED_ORIGIN"/>
-  <int value="143" label="NMF_INVALID_ID_CLOSE"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions" type="int">
@@ -89065,6 +89134,7 @@
       label="disable-infobar-for-protected-media-identifier"/>
   <int value="-1874908826" label="enable-instant-search-clicks"/>
   <int value="-1872989945" label="enable-webview-based-signin"/>
+  <int value="-1872867546" label="EnumerateAudioDevices:disabled"/>
   <int value="-1870961970" label="enable-filemanager-mtp"/>
   <int value="-1869845022" label="force-show-update-menu-item"/>
   <int value="-1867382602" label="WebRTC-H264WithOpenH264FFmpeg:enabled"/>
@@ -89231,6 +89301,7 @@
   <int value="-1052415111" label="malware-interstitial-v2"/>
   <int value="-1052219252" label="disable-captive-portal-bypass-proxy"/>
   <int value="-1041650038" label="enable-forced-migration-to-tabbed-mode"/>
+  <int value="-1039889738" label="NativeNotifications:enabled"/>
   <int value="-1039555838" label="GamepadExtensions:enabled"/>
   <int value="-1033738911" label="enable-mac-views-dialogs"/>
   <int value="-1028733699" label="MacViewsWebUIDialogs:disabled"/>
@@ -89675,6 +89746,7 @@
   <int value="1253698118" label="ash-disable-stable-overview-order"/>
   <int value="1257980502" label="disable-accelerated-video-decode"/>
   <int value="1268470658" label="disable-android-password-link"/>
+  <int value="1269940659" label="EnumerateAudioDevices:enabled"/>
   <int value="1272699563" label="enable-hosted-mode"/>
   <int value="1276209777" label="ntp-switch-to-existing-tab"/>
   <int value="1279584261" label="enable-carrier-switching"/>
@@ -89683,6 +89755,7 @@
   <int value="1294131571" label="disable-winrt-midi-api"/>
   <int value="1298981651" label="disable-new-task-manager"/>
   <int value="1300282719" label="OfflinePagesBackgroundLoading:enabled"/>
+  <int value="1302421166" label="NativeNotifications:disabled"/>
   <int value="1308537004" label="force-pnacl-subzero"/>
   <int value="1312025202" label="NTPOfflinePageSuggestions:disabled"/>
   <int value="1317562265" label="SeccompSandboxAndroid:disabled"/>
@@ -108283,8 +108356,8 @@
 <histogram_suffixes name="PageLoadEventConditions" separator=".">
   <suffix name="BeforeCommit"/>
   <suffix name="AfterCommit.BeforePaint"/>
-  <suffix name="AfterPaint.BeforeInteraction"/>
-  <suffix name="AfterPaint.Before1sDelayedInteraction"/>
+  <suffix base="true" name="AfterPaint.BeforeInteraction"/>
+  <suffix base="true" name="AfterPaint.Before1sDelayedInteraction"/>
   <suffix name="DuringParse"/>
   <affected-histogram name="PageLoad.AbortTiming.Background"/>
   <affected-histogram name="PageLoad.AbortTiming.ClientRedirect"/>
@@ -109597,6 +109670,41 @@
   <affected-histogram name="PreloadScanner.Counts2.Miss"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="SafeBrowsing.V4Store.Metrics" separator="."
+    ordering="prefix">
+  <suffix name="UrlMalware"/>
+  <suffix name="UrlSoceng"/>
+  <suffix name="UrlUws"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdate.Result"/>
+  <affected-histogram name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdate.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessFullUpdate.DecodeAdditions.Result"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessFullUpdate.DecodeAdditions.Time"/>
+  <affected-histogram name="SafeBrowsing.V4ProcessFullUpdate.MergeUpdate.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdate.Result"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdate.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.DecodeAdditions.Result"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.DecodeAdditions.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.DecodeRemovals.Result"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.DecodeRemovals.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.MergeUpdate.Time"/>
+  <affected-histogram name="SafeBrowsing.V4ReadFromDisk.ApplyUpdate.Result"/>
+  <affected-histogram name="SafeBrowsing.V4ReadFromDisk.ApplyUpdate.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ReadFromDisk.DecodeAdditions.Result"/>
+  <affected-histogram name="SafeBrowsing.V4ReadFromDisk.DecodeAdditions.Time"/>
+  <affected-histogram name="SafeBrowsing.V4ReadFromDisk.MergeUpdate.Time"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="SafeBrowsingLists" separator=".">
   <suffix name="Browse" label="Browse"/>
   <suffix name="Download" label="Download"/>
diff --git a/tools/metrics/histograms/print_style.py b/tools/metrics/histograms/print_style.py
index ee2025ce..ebd65b9 100644
--- a/tools/metrics/histograms/print_style.py
+++ b/tools/metrics/histograms/print_style.py
@@ -22,7 +22,7 @@
     'enums': [],
     # TODO(yiyaoliu): Remove fieldtrial related pieces when it is not used.
     'fieldtrial': ['name', 'separator', 'ordering'],
-    'histogram': ['name', 'enum', 'units'],
+    'histogram': ['base', 'name', 'enum', 'units'],
     'histogram-configuration': ['logsource'],
     'histogram_suffixes': ['name', 'separator', 'ordering'],
     'histogram_suffixes_list': [],
@@ -31,7 +31,7 @@
     'group': ['name', 'label'],
     'obsolete': [],
     'owner': [],
-    'suffix': ['name', 'label'],
+    'suffix': ['base', 'name', 'label'],
     'summary': [],
     'with-group': ['name'],
     'with-suffix': ['name'],
diff --git a/tools/perf/benchmarks/battor.py b/tools/perf/benchmarks/battor.py
index 052edd85..aeb863c 100644
--- a/tools/perf/benchmarks/battor.py
+++ b/tools/perf/benchmarks/battor.py
@@ -2,8 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import os
-
 from core import perf_benchmark
 from telemetry.timeline import chrome_trace_category_filter
 from telemetry.web_perf import timeline_based_measurement
@@ -23,11 +21,7 @@
     # TODO(charliea): Reenable the CPU tracing agent once it no longer causes
     # indefinite hangs on Windows.
     # https://crbug.com/647443
-    # TODO(charliea): Reenable BattOr tracing on the main perf waterfall once
-    # the BattOrs stop crashing as their SD cards fill up.
-    # crbug.com/652384
-    options.config.enable_battor_trace = (
-        os.environ.get('BUILDBOT_MASTERNAME') == 'chromium.perf.fyi')
+    options.config.enable_battor_trace = True
     options.config.enable_chrome_trace = True
     options.config.enable_atrace_trace = True
     options.config.atrace_config.categories = ['sched']
@@ -79,11 +73,7 @@
 
   def CreateTimelineBasedMeasurementOptions(self):
     options = timeline_based_measurement.Options()
-    # TODO(charliea): Reenable BattOr tracing on the main perf waterfall once
-    # the BattOrs stop crashing as their SD cards fill up.
-    # crbug.com/652384
-    options.config.enable_battor_trace = (
-        os.environ.get('BUILDBOT_MASTERNAME') == 'chromium.perf.fyi')
+    options.config.enable_battor_trace = True
     options.config.enable_chrome_trace = False
     options.config.chrome_trace_config.SetDefaultOverheadFilter()
     options.SetTimelineBasedMetrics(['powerMetric', 'clockSyncLatencyMetric'])
diff --git a/tools/perf/benchmarks/system_health.py b/tools/perf/benchmarks/system_health.py
index 4cbd8a9a..6303fbb 100644
--- a/tools/perf/benchmarks/system_health.py
+++ b/tools/perf/benchmarks/system_health.py
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import os
 import re
 
 from core import perf_benchmark
@@ -34,11 +33,7 @@
     options = timeline_based_measurement.Options(
         chrome_trace_category_filter.ChromeTraceCategoryFilter())
     options.config.chrome_trace_config.category_filter.AddFilterString('rail')
-    # TODO(charliea): Reenable BattOr tracing on the main perf waterfall once
-    # the BattOrs stop crashing as their SD cards fill up.
-    # crbug.com/652384
-    options.config.enable_battor_trace = (
-        os.environ.get('BUILDBOT_MASTERNAME') == 'chromium.perf.fyi')
+    options.config.enable_battor_trace = True
     options.config.enable_chrome_trace = True
     options.SetTimelineBasedMetrics(['clockSyncLatencyMetric', 'powerMetric'])
     return options
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index ed9f8cc80..e73d9a4 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -899,9 +899,10 @@
   COM_OBJECT_VALIDATE_1_ARG(object);
 
   if (riid == IID_IAccessible2) {
-    FOR_EACH_OBSERVER(IAccessible2UsageObserver,
-                      GetIAccessible2UsageObserverList(),
-                      OnIAccessible2Used());
+    for (IAccessible2UsageObserver& observer :
+         GetIAccessible2UsageObserverList()) {
+      observer.OnIAccessible2Used();
+    }
   }
 
   if (guidService == IID_IAccessible ||
diff --git a/ui/android/window_android.cc b/ui/android/window_android.cc
index 67c6cc1..7e40c8f 100644
--- a/ui/android/window_android.cc
+++ b/ui/android/window_android.cc
@@ -55,9 +55,8 @@
 }
 
 void WindowAndroid::OnCompositingDidCommit() {
-  FOR_EACH_OBSERVER(WindowAndroidObserver,
-                    observer_list_,
-                    OnCompositingDidCommit());
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnCompositingDidCommit();
 }
 
 void WindowAndroid::AddObserver(WindowAndroidObserver* observer) {
@@ -74,16 +73,14 @@
     DetachCompositor();
 
   compositor_ = compositor;
-  FOR_EACH_OBSERVER(WindowAndroidObserver,
-                    observer_list_,
-                    OnAttachCompositor());
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnAttachCompositor();
 }
 
 void WindowAndroid::DetachCompositor() {
   compositor_ = NULL;
-  FOR_EACH_OBSERVER(WindowAndroidObserver,
-                    observer_list_,
-                    OnDetachCompositor());
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnDetachCompositor();
   observer_list_.Clear();
 }
 
@@ -98,8 +95,8 @@
 }
 
 void WindowAndroid::Animate(base::TimeTicks begin_frame_time) {
-  FOR_EACH_OBSERVER(
-      WindowAndroidObserver, observer_list_, OnAnimate(begin_frame_time));
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnAnimate(begin_frame_time);
 }
 
 void WindowAndroid::OnVSync(JNIEnv* env,
@@ -109,10 +106,8 @@
   base::TimeTicks frame_time(base::TimeTicks::FromInternalValue(time_micros));
   base::TimeDelta vsync_period(
       base::TimeDelta::FromMicroseconds(period_micros));
-  FOR_EACH_OBSERVER(
-      WindowAndroidObserver,
-      observer_list_,
-      OnVSync(frame_time, vsync_period));
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnVSync(frame_time, vsync_period);
   if (compositor_)
     compositor_->OnVSync(frame_time, vsync_period);
 }
@@ -120,18 +115,20 @@
 void WindowAndroid::OnVisibilityChanged(JNIEnv* env,
                                         const JavaParamRef<jobject>& obj,
                                         bool visible) {
-  FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_,
-                    OnRootWindowVisibilityChanged(visible));
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnRootWindowVisibilityChanged(visible);
 }
 
 void WindowAndroid::OnActivityStopped(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
-  FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_, OnActivityStopped());
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnActivityStopped();
 }
 
 void WindowAndroid::OnActivityStarted(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
-  FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_, OnActivityStarted());
+  for (WindowAndroidObserver& observer : observer_list_)
+    observer.OnActivityStarted();
 }
 
 bool WindowAndroid::HasPermission(const std::string& permission) {
diff --git a/ui/aura/env.cc b/ui/aura/env.cc
index 058d94a..07c1138 100644
--- a/ui/aura/env.cc
+++ b/ui/aura/env.cc
@@ -36,7 +36,8 @@
 // Env, public:
 
 Env::~Env() {
-  FOR_EACH_OBSERVER(EnvObserver, observers_, OnWillDestroyEnv());
+  for (EnvObserver& observer : observers_)
+    observer.OnWillDestroyEnv();
   DCHECK_EQ(this, lazy_tls_ptr.Pointer()->Get());
   lazy_tls_ptr.Pointer()->Set(NULL);
 }
@@ -101,15 +102,18 @@
 }
 
 void Env::NotifyWindowInitialized(Window* window) {
-  FOR_EACH_OBSERVER(EnvObserver, observers_, OnWindowInitialized(window));
+  for (EnvObserver& observer : observers_)
+    observer.OnWindowInitialized(window);
 }
 
 void Env::NotifyHostInitialized(WindowTreeHost* host) {
-  FOR_EACH_OBSERVER(EnvObserver, observers_, OnHostInitialized(host));
+  for (EnvObserver& observer : observers_)
+    observer.OnHostInitialized(host);
 }
 
 void Env::NotifyHostActivated(WindowTreeHost* host) {
-  FOR_EACH_OBSERVER(EnvObserver, observers_, OnHostActivated(host));
+  for (EnvObserver& observer : observers_)
+    observer.OnHostActivated(host);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/aura/test/test_cursor_client.cc b/ui/aura/test/test_cursor_client.cc
index 123046c..54b0e07 100644
--- a/ui/aura/test/test_cursor_client.cc
+++ b/ui/aura/test/test_cursor_client.cc
@@ -33,14 +33,14 @@
 
 void TestCursorClient::ShowCursor() {
   visible_ = true;
-  FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_,
-                    OnCursorVisibilityChanged(true));
+  for (aura::client::CursorClientObserver& observer : observers_)
+    observer.OnCursorVisibilityChanged(true);
 }
 
 void TestCursorClient::HideCursor() {
   visible_ = false;
-  FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_,
-                    OnCursorVisibilityChanged(false));
+  for (aura::client::CursorClientObserver& observer : observers_)
+    observer.OnCursorVisibilityChanged(false);
 }
 
 void TestCursorClient::SetCursorSet(ui::CursorSetType cursor_set) {
diff --git a/ui/aura/test/test_focus_client.cc b/ui/aura/test/test_focus_client.cc
index 662f9b1..027afe6 100644
--- a/ui/aura/test/test_focus_client.cc
+++ b/ui/aura/test/test_focus_client.cc
@@ -44,9 +44,8 @@
   if (focused_window_)
     observer_manager_.Add(focused_window_);
 
-  FOR_EACH_OBSERVER(aura::client::FocusChangeObserver,
-                     focus_observers_,
-                     OnWindowFocused(focused_window_, lost_focus));
+  for (aura::client::FocusChangeObserver& observer : focus_observers_)
+    observer.OnWindowFocused(focused_window_, lost_focus);
   client::FocusChangeObserver* observer =
       client::GetFocusChangeObserver(old_focused_window);
   if (observer)
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index ead05dd..be94111 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -109,7 +109,8 @@
   // Let the delegate know we're in the processing of destroying.
   if (delegate_)
     delegate_->OnWindowDestroying(this);
-  FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDestroying(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowDestroying(this);
 
   // While we are being destroyed, our target handler may also be in the
   // process of destruction or already destroyed, so do not forward any
@@ -193,9 +194,8 @@
   if (title == title_)
     return;
   title_ = title;
-  FOR_EACH_OBSERVER(WindowObserver,
-                    observers_,
-                    OnWindowTitleChanged(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowTitleChanged(this);
 }
 
 void Window::SetTransparent(bool transparent) {
@@ -276,11 +276,11 @@
 }
 
 void Window::SetTransform(const gfx::Transform& transform) {
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowTransforming(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowTransforming(this);
   layer()->SetTransform(transform);
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowTransformed(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowTransformed(this);
   NotifyAncestorWindowTransformed(this);
 }
 
@@ -384,7 +384,8 @@
   children_.push_back(child);
   if (layout_manager_)
     layout_manager_->OnWindowAddedToLayout(child);
-  FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowAdded(child));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowAdded(child);
   child->OnParentChanged();
 
   Window* root_window = GetRootWindow();
@@ -677,8 +678,8 @@
     prop_value.deallocator = deallocator;
     prop_map_[key] = prop_value;
   }
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowPropertyChanged(this, key, old));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowPropertyChanged(this, key, old);
   return old;
 }
 
@@ -724,8 +725,8 @@
   if (visible == layer()->GetTargetVisibility())
     return;  // No change.
 
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowVisibilityChanging(this, visible));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowVisibilityChanging(this, visible);
 
   client::VisibilityClient* visibility_client =
       client::GetVisibilityClient(this);
@@ -811,7 +812,8 @@
 void Window::RemoveChildImpl(Window* child, Window* new_parent) {
   if (layout_manager_)
     layout_manager_->OnWillRemoveWindowFromLayout(child);
-  FOR_EACH_OBSERVER(WindowObserver, observers_, OnWillRemoveWindow(child));
+  for (WindowObserver& observer : observers_)
+    observer.OnWillRemoveWindow(child);
   Window* root_window = child->GetRootWindow();
   Window* new_root_window = new_parent ? new_parent->GetRootWindow() : NULL;
   if (root_window && root_window != new_root_window)
@@ -829,8 +831,8 @@
 }
 
 void Window::OnParentChanged() {
-  FOR_EACH_OBSERVER(
-      WindowObserver, observers_, OnWindowParentChanged(this, parent_));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowParentChanged(this, parent_);
 }
 
 void Window::StackChildRelativeTo(Window* child,
@@ -881,12 +883,13 @@
 }
 
 void Window::OnStackingChanged() {
-  FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowStackingChanged(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowStackingChanged(this);
 }
 
 void Window::NotifyRemovingFromRootWindow(Window* new_root) {
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowRemovingFromRootWindow(this, new_root));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowRemovingFromRootWindow(this, new_root);
   for (Window::Windows::const_iterator it = children_.begin();
        it != children_.end(); ++it) {
     (*it)->NotifyRemovingFromRootWindow(new_root);
@@ -894,8 +897,8 @@
 }
 
 void Window::NotifyAddedToRootWindow() {
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowAddedToRootWindow(this));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowAddedToRootWindow(this);
   for (Window::Windows::const_iterator it = children_.begin();
        it != children_.end(); ++it) {
     (*it)->NotifyAddedToRootWindow();
@@ -963,8 +966,8 @@
   // exit without further access to any members.
   WindowTracker tracker;
   tracker.Add(this);
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnWindowVisibilityChanged(target, visible));
+  for (WindowObserver& observer : observers_)
+    observer.OnWindowVisibilityChanged(target, visible);
   return tracker.Contains(this);
 }
 
@@ -1002,8 +1005,8 @@
 }
 
 void Window::NotifyAncestorWindowTransformed(Window* source) {
-  FOR_EACH_OBSERVER(WindowObserver, observers_,
-                    OnAncestorWindowTransformed(source, this));
+  for (WindowObserver& observer : observers_)
+    observer.OnAncestorWindowTransformed(source, this);
   for (Window::Windows::const_iterator it = children_.begin();
        it != children_.end(); ++it) {
     (*it)->NotifyAncestorWindowTransformed(source);
@@ -1029,9 +1032,8 @@
 
 void Window::OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) {
   DCHECK(layer());
-  FOR_EACH_OBSERVER(WindowObserver,
-                    observers_,
-                    OnDelegatedFrameDamage(this, damage_rect_in_dip));
+  for (WindowObserver& observer : observers_)
+    observer.OnDelegatedFrameDamage(this, damage_rect_in_dip);
 }
 
 void Window::OnLayerBoundsChanged(const gfx::Rect& old_bounds) {
diff --git a/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc
index afa08aa..2da90f8 100644
--- a/ui/aura/window_tree_host.cc
+++ b/ui/aura/window_tree_host.cc
@@ -270,8 +270,8 @@
   TRACE_EVENT1("ui", "WindowTreeHost::OnHostMoved",
                "origin", new_location.ToString());
 
-  FOR_EACH_OBSERVER(WindowTreeHostObserver, observers_,
-                    OnHostMoved(this, new_location));
+  for (WindowTreeHostObserver& observer : observers_)
+    observer.OnHostMoved(this, new_location);
 }
 
 void WindowTreeHost::OnHostResized(const gfx::Size& new_size) {
@@ -287,17 +287,18 @@
   // The layer, and the observers should be notified of the
   // transformed size of the root window.
   UpdateRootWindowSize(layer_size);
-  FOR_EACH_OBSERVER(WindowTreeHostObserver, observers_, OnHostResized(this));
+  for (WindowTreeHostObserver& observer : observers_)
+    observer.OnHostResized(this);
 }
 
 void WindowTreeHost::OnHostWorkspaceChanged() {
-  FOR_EACH_OBSERVER(WindowTreeHostObserver, observers_,
-                    OnHostWorkspaceChanged(this));
+  for (WindowTreeHostObserver& observer : observers_)
+    observer.OnHostWorkspaceChanged(this);
 }
 
 void WindowTreeHost::OnHostCloseRequested() {
-  FOR_EACH_OBSERVER(WindowTreeHostObserver, observers_,
-                    OnHostCloseRequested(this));
+  for (WindowTreeHostObserver& observer : observers_)
+    observer.OnHostCloseRequested(this);
 }
 
 void WindowTreeHost::OnHostActivated() {
diff --git a/ui/base/clipboard/clipboard_monitor.cc b/ui/base/clipboard/clipboard_monitor.cc
index 8991714..984e766 100644
--- a/ui/base/clipboard/clipboard_monitor.cc
+++ b/ui/base/clipboard/clipboard_monitor.cc
@@ -23,7 +23,8 @@
 
 void ClipboardMonitor::NotifyClipboardDataChanged() {
   DCHECK(CalledOnValidThread());
-  FOR_EACH_OBSERVER(ClipboardObserver, observers_, OnClipboardDataChanged());
+  for (ClipboardObserver& observer : observers_)
+    observer.OnClipboardDataChanged();
 }
 
 void ClipboardMonitor::AddObserver(ClipboardObserver* observer) {
diff --git a/ui/base/ime/chromeos/ime_keyboard.cc b/ui/base/ime/chromeos/ime_keyboard.cc
index 581a3c5..24273f8 100644
--- a/ui/base/ime/chromeos/ime_keyboard.cc
+++ b/ui/base/ime/chromeos/ime_keyboard.cc
@@ -79,8 +79,8 @@
   bool old_state = caps_lock_is_enabled_;
   caps_lock_is_enabled_ = enable_caps_lock;
   if (old_state != enable_caps_lock) {
-    FOR_EACH_OBSERVER(ImeKeyboard::Observer, observers_,
-                      OnCapsLockChanged(enable_caps_lock));
+    for (ImeKeyboard::Observer& observer : observers_)
+      observer.OnCapsLockChanged(enable_caps_lock);
   }
 }
 
diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc
index 37c4d90a..9d43d852 100644
--- a/ui/base/ime/input_method_base.cc
+++ b/ui/base/ime/input_method_base.cc
@@ -22,9 +22,8 @@
       text_input_client_(nullptr) {}
 
 InputMethodBase::~InputMethodBase() {
-  FOR_EACH_OBSERVER(InputMethodObserver,
-                    observer_list_,
-                    OnInputMethodDestroyed(this));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnInputMethodDestroyed(this);
   if (ui::IMEBridge::Get() &&
       ui::IMEBridge::Get()->GetInputContextHandler() == this)
     ui::IMEBridge::Get()->SetInputContextHandler(nullptr);
@@ -93,7 +92,8 @@
 }
 
 void InputMethodBase::ShowImeIfNeeded() {
-  FOR_EACH_OBSERVER(InputMethodObserver, observer_list_, OnShowImeIfNeeded());
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnShowImeIfNeeded();
 }
 
 void InputMethodBase::AddObserver(InputMethodObserver* observer) {
@@ -128,15 +128,14 @@
 
 void InputMethodBase::NotifyTextInputStateChanged(
     const TextInputClient* client) {
-  FOR_EACH_OBSERVER(InputMethodObserver,
-                    observer_list_,
-                    OnTextInputStateChanged(client));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnTextInputStateChanged(client);
 }
 
 void InputMethodBase::NotifyTextInputCaretBoundsChanged(
     const TextInputClient* client) {
-  FOR_EACH_OBSERVER(
-      InputMethodObserver, observer_list_, OnCaretBoundsChanged(client));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnCaretBoundsChanged(client);
 }
 
 void InputMethodBase::SetFocusedTextInputClientInternal(
diff --git a/ui/base/ime/mock_input_method.cc b/ui/base/ime/mock_input_method.cc
index fbd8bcf8..d097005b 100644
--- a/ui/base/ime/mock_input_method.cc
+++ b/ui/base/ime/mock_input_method.cc
@@ -14,8 +14,8 @@
 }
 
 MockInputMethod::~MockInputMethod() {
-  FOR_EACH_OBSERVER(InputMethodObserver, observer_list_,
-                    OnInputMethodDestroyed(this));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnInputMethodDestroyed(this);
 }
 
 void MockInputMethod::SetDelegate(internal::InputMethodDelegate* delegate) {
@@ -45,11 +45,13 @@
 }
 
 void MockInputMethod::OnFocus() {
-  FOR_EACH_OBSERVER(InputMethodObserver, observer_list_, OnFocus());
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnFocus();
 }
 
 void MockInputMethod::OnBlur() {
-  FOR_EACH_OBSERVER(InputMethodObserver, observer_list_, OnBlur());
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnBlur();
 }
 
 bool MockInputMethod::OnUntranslatedIMEMessage(const base::NativeEvent& event,
@@ -60,18 +62,15 @@
 }
 
 void MockInputMethod::OnTextInputTypeChanged(const TextInputClient* client) {
-  FOR_EACH_OBSERVER(InputMethodObserver,
-                    observer_list_,
-                    OnTextInputTypeChanged(client));
-  FOR_EACH_OBSERVER(InputMethodObserver,
-                    observer_list_,
-                    OnTextInputStateChanged(client));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnTextInputTypeChanged(client);
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnTextInputStateChanged(client);
 }
 
 void MockInputMethod::OnCaretBoundsChanged(const TextInputClient* client) {
-  FOR_EACH_OBSERVER(InputMethodObserver,
-                    observer_list_,
-                    OnCaretBoundsChanged(client));
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnCaretBoundsChanged(client);
 }
 
 void MockInputMethod::CancelComposition(const TextInputClient* client) {
@@ -105,7 +104,8 @@
 }
 
 void MockInputMethod::ShowImeIfNeeded() {
-  FOR_EACH_OBSERVER(InputMethodObserver, observer_list_, OnShowImeIfNeeded());
+  for (InputMethodObserver& observer : observer_list_)
+    observer.OnShowImeIfNeeded();
 }
 
 void MockInputMethod::AddObserver(InputMethodObserver* observer) {
diff --git a/ui/base/user_activity/user_activity_detector.cc b/ui/base/user_activity/user_activity_detector.cc
index 29dec4e8..bfda1cfd 100644
--- a/ui/base/user_activity/user_activity_detector.cc
+++ b/ui/base/user_activity/user_activity_detector.cc
@@ -123,7 +123,8 @@
       kNotifyIntervalMs) {
     if (VLOG_IS_ON(1))
       VLOG(1) << "Reporting user activity: " << GetEventDebugString(event);
-    FOR_EACH_OBSERVER(UserActivityObserver, observers_, OnUserActivity(event));
+    for (UserActivityObserver& observer : observers_)
+      observer.OnUserActivity(event);
     last_observer_notification_time_ = now;
   }
 }
diff --git a/ui/base/win/osk_display_manager.cc b/ui/base/win/osk_display_manager.cc
index c3abb9fc..0373b48 100644
--- a/ui/base/win/osk_display_manager.cc
+++ b/ui/base/win/osk_display_manager.cc
@@ -219,8 +219,8 @@
   DCHECK(!osk_visible_notification_received_);
   osk_visible_notification_received_ = true;
 
-  FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
-                    OnKeyboardVisible(osk_rect_pixels_));
+  for (OnScreenKeyboardObserver& observer : observers_)
+    observer.OnKeyboardVisible(osk_rect_pixels_);
 
   // Now that the keyboard is visible, run the task to detect if it was hidden.
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -231,8 +231,8 @@
 
 void OnScreenKeyboardDetector::HandleKeyboardHidden() {
   osk_visible_notification_received_ = false;
-  FOR_EACH_OBSERVER(OnScreenKeyboardObserver, observers_,
-                    OnKeyboardHidden(osk_rect_pixels_));
+  for (OnScreenKeyboardObserver& observer : observers_)
+    observer.OnKeyboardHidden(osk_rect_pixels_);
   ClearObservers();
 }
 
diff --git a/ui/chromeos/ime/candidate_window_view.cc b/ui/chromeos/ime/candidate_window_view.cc
index 5c42421..dcfd3589 100644
--- a/ui/chromeos/ime/candidate_window_view.cc
+++ b/ui/chromeos/ime/candidate_window_view.cc
@@ -409,7 +409,8 @@
                                         const ui::Event& event) {
   for (size_t i = 0; i < candidate_views_.size(); ++i) {
     if (sender == candidate_views_[i]) {
-      FOR_EACH_OBSERVER(Observer, observers_, OnCandidateCommitted(i));
+      for (Observer& observer : observers_)
+        observer.OnCandidateCommitted(i);
       return;
     }
   }
diff --git a/ui/chromeos/ime/input_method_menu_manager.cc b/ui/chromeos/ime/input_method_menu_manager.cc
index f4032f8..08c053f 100644
--- a/ui/chromeos/ime/input_method_menu_manager.cc
+++ b/ui/chromeos/ime/input_method_menu_manager.cc
@@ -35,9 +35,8 @@
 void InputMethodMenuManager::SetCurrentInputMethodMenuItemList(
     const InputMethodMenuItemList& menu_list) {
   menu_list_ = menu_list;
-  FOR_EACH_OBSERVER(InputMethodMenuManager::Observer,
-                    observers_,
-                    InputMethodMenuItemChanged(this));
+  for (InputMethodMenuManager::Observer& observer : observers_)
+    observer.InputMethodMenuItemChanged(this);
 }
 
 bool InputMethodMenuManager::HasInputMethodMenuItemForKey(
diff --git a/ui/chromeos/network/network_icon_animation.cc b/ui/chromeos/network/network_icon_animation.cc
index 055a10b0..9ed3a47 100644
--- a/ui/chromeos/network/network_icon_animation.cc
+++ b/ui/chromeos/network/network_icon_animation.cc
@@ -27,7 +27,8 @@
     const gfx::Animation* animation) {
   if (animation != &animation_)
     return;
-  FOR_EACH_OBSERVER(AnimationObserver, observers_, NetworkIconChanged());
+  for (AnimationObserver& observer : observers_)
+    observer.NetworkIconChanged();
 }
 
 double NetworkIconAnimation::GetAnimation() {
diff --git a/ui/display/chromeos/display_configurator.cc b/ui/display/chromeos/display_configurator.cc
index b0bd0a0..6c30623 100644
--- a/ui/display/chromeos/display_configurator.cc
+++ b/ui/display/chromeos/display_configurator.cc
@@ -1109,18 +1109,17 @@
     bool success,
     MultipleDisplayState attempted_state) {
   if (success) {
-    FOR_EACH_OBSERVER(
-        Observer, observers_, OnDisplayModeChanged(cached_displays_));
+    for (Observer& observer : observers_)
+      observer.OnDisplayModeChanged(cached_displays_);
   } else {
-    FOR_EACH_OBSERVER(
-        Observer, observers_, OnDisplayModeChangeFailed(cached_displays_,
-                                                        attempted_state));
+    for (Observer& observer : observers_)
+      observer.OnDisplayModeChangeFailed(cached_displays_, attempted_state);
   }
 }
 
 void DisplayConfigurator::NotifyPowerStateObservers() {
-  FOR_EACH_OBSERVER(
-      Observer, observers_, OnPowerStateChanged(current_power_state_));
+  for (Observer& observer : observers_)
+    observer.OnPowerStateChanged(current_power_state_);
 }
 
 int64_t DisplayConfigurator::AddVirtualDisplay(const gfx::Size& display_size) {
diff --git a/ui/display/chromeos/x11/native_display_delegate_x11.cc b/ui/display/chromeos/x11/native_display_delegate_x11.cc
index 7fe27ffc5..3cbfe69 100644
--- a/ui/display/chromeos/x11/native_display_delegate_x11.cc
+++ b/ui/display/chromeos/x11/native_display_delegate_x11.cc
@@ -90,8 +90,8 @@
     return delegate_->cached_outputs_.get();
   }
   void NotifyDisplayObservers() override {
-    FOR_EACH_OBSERVER(
-        NativeDisplayObserver, delegate_->observers_, OnConfigurationChanged());
+    for (NativeDisplayObserver& observer : delegate_->observers_)
+      observer.OnConfigurationChanged();
   }
 
  private:
diff --git a/ui/display/display_change_notifier.cc b/ui/display/display_change_notifier.cc
index 9410ca7..626c86ee 100644
--- a/ui/display/display_change_notifier.cc
+++ b/ui/display/display_change_notifier.cc
@@ -48,8 +48,8 @@
   for (; old_it != old_displays.end(); ++old_it) {
     if (std::find_if(new_displays.begin(), new_displays.end(),
                      DisplayComparator(*old_it)) == new_displays.end()) {
-      FOR_EACH_OBSERVER(DisplayObserver, observer_list_,
-                        OnDisplayRemoved(*old_it));
+      for (DisplayObserver& observer : observer_list_)
+        observer.OnDisplayRemoved(*old_it);
     }
   }
 
@@ -61,8 +61,8 @@
         old_displays.begin(), old_displays.end(), DisplayComparator(*new_it));
 
     if (old_it == old_displays.end()) {
-      FOR_EACH_OBSERVER(DisplayObserver, observer_list_,
-                        OnDisplayAdded(*new_it));
+      for (DisplayObserver& observer : observer_list_)
+        observer.OnDisplayAdded(*new_it);
       continue;
     }
 
@@ -81,8 +81,8 @@
       metrics |= DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR;
 
     if (metrics != DisplayObserver::DISPLAY_METRIC_NONE) {
-      FOR_EACH_OBSERVER(DisplayObserver, observer_list_,
-                        OnDisplayMetricsChanged(*new_it, metrics));
+      for (DisplayObserver& observer : observer_list_)
+        observer.OnDisplayMetricsChanged(*new_it, metrics);
     }
   }
 }
diff --git a/ui/display/display_list.cc b/ui/display/display_list.cc
index 842808a..6aea752 100644
--- a/ui/display/display_list.cc
+++ b/ui/display/display_list.cc
@@ -92,8 +92,8 @@
         display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR;
   }
   if (should_notify_observers()) {
-    FOR_EACH_OBSERVER(display::DisplayObserver, observers_,
-                      OnDisplayMetricsChanged(*local_display, changed_values));
+    for (display::DisplayObserver& observer : observers_)
+      observer.OnDisplayMetricsChanged(*local_display, changed_values);
   }
 }
 
@@ -103,8 +103,8 @@
   if (type == Type::PRIMARY)
     primary_display_index_ = static_cast<int>(displays_.size()) - 1;
   if (should_notify_observers()) {
-    FOR_EACH_OBSERVER(display::DisplayObserver, observers_,
-                      OnDisplayAdded(display));
+    for (display::DisplayObserver& observer : observers_)
+      observer.OnDisplayAdded(display);
   }
 }
 
@@ -123,8 +123,8 @@
   const display::Display display = *iter;
   displays_.erase(iter);
   if (should_notify_observers()) {
-    FOR_EACH_OBSERVER(display::DisplayObserver, observers_,
-                      OnDisplayRemoved(display));
+    for (display::DisplayObserver& observer : observers_)
+      observer.OnDisplayRemoved(display);
   }
 }
 
diff --git a/ui/display/fake_display_delegate.cc b/ui/display/fake_display_delegate.cc
index ccfc64d..2f1503d 100644
--- a/ui/display/fake_display_delegate.cc
+++ b/ui/display/fake_display_delegate.cc
@@ -228,8 +228,8 @@
   if (!initialized_)
     return;
 
-  FOR_EACH_OBSERVER(ui::NativeDisplayObserver, observers_,
-                    OnConfigurationChanged());
+  for (ui::NativeDisplayObserver& observer : observers_)
+    observer.OnConfigurationChanged();
 }
 
 }  // namespace display
diff --git a/ui/events/devices/device_data_manager.cc b/ui/events/devices/device_data_manager.cc
index b54cd56..94bbeb1 100644
--- a/ui/events/devices/device_data_manager.cc
+++ b/ui/events/devices/device_data_manager.cc
@@ -12,9 +12,10 @@
 #include "ui/gfx/geometry/point3_f.h"
 
 // This macro provides the implementation for the observer notification methods.
-#define NOTIFY_OBSERVERS(method_decl, observer_call)                        \
-  void DeviceDataManager::method_decl {                                     \
-    FOR_EACH_OBSERVER(InputDeviceEventObserver, observers_, observer_call); \
+#define NOTIFY_OBSERVERS(method_decl, observer_call)      \
+  void DeviceDataManager::method_decl {                   \
+    for (InputDeviceEventObserver& observer : observers_) \
+      observer.observer_call;                             \
   }
 
 namespace ui {
diff --git a/ui/events/ozone/device/device_manager_manual.cc b/ui/events/ozone/device/device_manager_manual.cc
index 65d1c253..aabb0a7 100644
--- a/ui/events/ozone/device/device_manager_manual.cc
+++ b/ui/events/ozone/device/device_manager_manual.cc
@@ -66,7 +66,8 @@
   for (; it != result->end(); ++it) {
     devices_.push_back(*it);
     DeviceEvent event(DeviceEvent::INPUT, DeviceEvent::ADD, *it);
-    FOR_EACH_OBSERVER(DeviceEventObserver, observers_, OnDeviceEvent(event));
+    for (DeviceEventObserver& observer : observers_)
+      observer.OnDeviceEvent(event);
   }
 }
 
diff --git a/ui/events/ozone/device/udev/device_manager_udev.cc b/ui/events/ozone/device/udev/device_manager_udev.cc
index 47776dac..2f8ee26 100644
--- a/ui/events/ozone/device/udev/device_manager_udev.cc
+++ b/ui/events/ozone/device/udev/device_manager_udev.cc
@@ -148,8 +148,8 @@
 
   std::unique_ptr<DeviceEvent> event = ProcessMessage(device.get());
   if (event)
-    FOR_EACH_OBSERVER(
-        DeviceEventObserver, observers_, OnDeviceEvent(*event.get()));
+    for (DeviceEventObserver& observer : observers_)
+      observer.OnDeviceEvent(*event.get());
 }
 
 void DeviceManagerUdev::OnFileCanWriteWithoutBlocking(int fd) {
diff --git a/ui/events/platform/platform_event_source.cc b/ui/events/platform/platform_event_source.cc
index dfab7fc..9be6630e 100644
--- a/ui/events/platform/platform_event_source.cc
+++ b/ui/events/platform/platform_event_source.cc
@@ -69,8 +69,8 @@
 uint32_t PlatformEventSource::DispatchEvent(PlatformEvent platform_event) {
   uint32_t action = POST_DISPATCH_PERFORM_DEFAULT;
 
-  FOR_EACH_OBSERVER(PlatformEventObserver, observers_,
-                    WillProcessEvent(platform_event));
+  for (PlatformEventObserver& observer : observers_)
+    observer.WillProcessEvent(platform_event);
   // Give the overridden dispatcher a chance to dispatch the event first.
   if (overridden_dispatcher_)
     action = overridden_dispatcher_->DispatchEvent(platform_event);
@@ -83,8 +83,8 @@
         break;
     }
   }
-  FOR_EACH_OBSERVER(PlatformEventObserver, observers_,
-                    DidProcessEvent(platform_event));
+  for (PlatformEventObserver& observer : observers_)
+    observer.DidProcessEvent(platform_event);
 
   // If an overridden dispatcher has been destroyed, then the platform
   // event-source should halt dispatching the current stream of events, and wait
diff --git a/ui/gfx/sys_color_change_listener.cc b/ui/gfx/sys_color_change_listener.cc
index 8c6cd04e7..0c1f7928 100644
--- a/ui/gfx/sys_color_change_listener.cc
+++ b/ui/gfx/sys_color_change_listener.cc
@@ -94,7 +94,8 @@
   if (message == WM_SYSCOLORCHANGE ||
       (message == WM_SETTINGCHANGE && wparam == SPI_SETHIGHCONTRAST)) {
     UpdateInvertedColorScheme();
-    FOR_EACH_OBSERVER(SysColorChangeListener, listeners_, OnSysColorChange());
+    for (SysColorChangeListener& observer : listeners_)
+      observer.OnSysColorChange();
   }
 }
 
diff --git a/ui/gfx/win/singleton_hwnd.cc b/ui/gfx/win/singleton_hwnd.cc
index 8142187..7d3aa6e 100644
--- a/ui/gfx/win/singleton_hwnd.cc
+++ b/ui/gfx/win/singleton_hwnd.cc
@@ -21,9 +21,8 @@
                                          LPARAM lparam,
                                          LRESULT& result,
                                          DWORD msg_map_id) {
-  FOR_EACH_OBSERVER(SingletonHwndObserver,
-                    observer_list_,
-                    OnWndProc(window, message, wparam, lparam));
+  for (SingletonHwndObserver& observer : observer_list_)
+    observer.OnWndProc(window, message, wparam, lparam);
   return false;
 }
 
@@ -42,7 +41,8 @@
     DestroyWindow(hwnd());
 
   // Tell all of our current observers to clean themselves up.
-  FOR_EACH_OBSERVER(SingletonHwndObserver, observer_list_, ClearWndProc());
+  for (SingletonHwndObserver& observer : observer_list_)
+    observer.ClearWndProc();
 }
 
 void SingletonHwnd::AddObserver(SingletonHwndObserver* observer) {
diff --git a/ui/gl/gpu_switching_manager.cc b/ui/gl/gpu_switching_manager.cc
index 5fdb3cc..9718114 100644
--- a/ui/gl/gpu_switching_manager.cc
+++ b/ui/gl/gpu_switching_manager.cc
@@ -128,7 +128,8 @@
 }
 
 void GpuSwitchingManager::NotifyGpuSwitched() {
-  FOR_EACH_OBSERVER(GpuSwitchingObserver, observer_list_, OnGpuSwitched());
+  for (GpuSwitchingObserver& observer : observer_list_)
+    observer.OnGpuSwitched();
 }
 
 gl::GpuPreference GpuSwitchingManager::AdjustGpuPreference(
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index 35eea587..de8bcbe 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -188,8 +188,8 @@
   }
   if (input_method_)
     input_method_->RemoveObserver(this);
-  FOR_EACH_OBSERVER(KeyboardControllerObserver, observer_list_,
-                    OnKeyboardClosed());
+  for (KeyboardControllerObserver& observer : observer_list_)
+    observer.OnKeyboardClosed();
   ui_->SetController(nullptr);
 }
 
@@ -221,9 +221,8 @@
     const gfx::Rect& new_bounds) {
   current_keyboard_bounds_ = new_bounds;
   if (ui_->HasKeyboardWindow() && ui_->GetKeyboardWindow()->IsVisible()) {
-    FOR_EACH_OBSERVER(KeyboardControllerObserver,
-                      observer_list_,
-                      OnKeyboardBoundsChanging(new_bounds));
+    for (KeyboardControllerObserver& observer : observer_list_)
+      observer.OnKeyboardBoundsChanging(new_bounds);
     if (keyboard::IsKeyboardOverscrollEnabled())
       ui_->InitInsets(new_bounds);
     else
@@ -488,8 +487,8 @@
 
 void KeyboardController::HideAnimationFinished() {
   ui_->HideKeyboardContainer(container_.get());
-  FOR_EACH_OBSERVER(KeyboardControllerObserver, observer_list_,
-                    OnKeyboardHidden());
+  for (KeyboardControllerObserver& observer : observer_list_)
+    observer.OnKeyboardHidden();
 }
 
 }  // namespace keyboard
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index e4911c9..9661318 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -35,8 +35,8 @@
 }
 
 void NativeTheme::NotifyObservers() {
-  FOR_EACH_OBSERVER(NativeThemeObserver, native_theme_observers_,
-                    OnNativeThemeUpdated(this));
+  for (NativeThemeObserver& observer : native_theme_observers_)
+    observer.OnNativeThemeUpdated(this);
 }
 
 NativeTheme::NativeTheme()
diff --git a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
index 8d619eb..75096a0 100644
--- a/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
+++ b/ui/ozone/platform/drm/host/drm_gpu_platform_support_host.cc
@@ -113,8 +113,8 @@
   send_runner_ = send_runner;
   send_callback_ = send_callback;
 
-  FOR_EACH_OBSERVER(GpuThreadObserver, gpu_thread_observers_,
-                    OnGpuThreadReady());
+  for (GpuThreadObserver& observer : gpu_thread_observers_)
+    observer.OnGpuThreadReady();
 
   // The cursor is special since it will process input events on the IO thread
   // and can by-pass the UI thread. This means that we need to special case it
@@ -133,8 +133,8 @@
     host_id_ = -1;
     send_runner_ = nullptr;
     send_callback_.Reset();
-    FOR_EACH_OBSERVER(GpuThreadObserver, gpu_thread_observers_,
-                      OnGpuThreadRetired());
+    for (GpuThreadObserver& observer : gpu_thread_observers_)
+      observer.OnGpuThreadRetired();
   }
 
 }
diff --git a/ui/ozone/platform/drm/host/drm_native_display_delegate.cc b/ui/ozone/platform/drm/host/drm_native_display_delegate.cc
index 45fbc84..0a16a7e 100644
--- a/ui/ozone/platform/drm/host/drm_native_display_delegate.cc
+++ b/ui/ozone/platform/drm/host/drm_native_display_delegate.cc
@@ -21,8 +21,8 @@
 }
 
 void DrmNativeDisplayDelegate::OnConfigurationChanged() {
-  FOR_EACH_OBSERVER(NativeDisplayObserver, observers_,
-                    OnConfigurationChanged());
+  for (NativeDisplayObserver& observer : observers_)
+    observer.OnConfigurationChanged();
 }
 
 void DrmNativeDisplayDelegate::Initialize() {
diff --git a/ui/ozone/platform/drm/mus_thread_proxy.cc b/ui/ozone/platform/drm/mus_thread_proxy.cc
index dd1c5d74..c70850a 100644
--- a/ui/ozone/platform/drm/mus_thread_proxy.cc
+++ b/ui/ozone/platform/drm/mus_thread_proxy.cc
@@ -42,8 +42,8 @@
 
 MusThreadProxy::~MusThreadProxy() {
   DCHECK(on_window_server_thread_.CalledOnValidThread());
-  FOR_EACH_OBSERVER(GpuThreadObserver, gpu_thread_observers_,
-                    OnGpuThreadRetired());
+  for (GpuThreadObserver& observer : gpu_thread_observers_)
+    observer.OnGpuThreadRetired();
 }
 
 // This is configured on the GPU thread.
@@ -74,8 +74,8 @@
 
 void MusThreadProxy::RunObservers() {
   DCHECK(on_window_server_thread_.CalledOnValidThread());
-  FOR_EACH_OBSERVER(GpuThreadObserver, gpu_thread_observers_,
-                    OnGpuThreadReady());
+  for (GpuThreadObserver& observer : gpu_thread_observers_)
+    observer.OnGpuThreadReady();
 }
 
 void MusThreadProxy::AddGpuThreadObserver(GpuThreadObserver* observer) {
diff --git a/ui/snapshot/screenshot_grabber.cc b/ui/snapshot/screenshot_grabber.cc
index ebccaae..7b35748a 100644
--- a/ui/snapshot/screenshot_grabber.cc
+++ b/ui/snapshot/screenshot_grabber.cc
@@ -187,8 +187,8 @@
 #if defined(USE_AURA)
   cursor_hider_.reset();
 #endif
-  FOR_EACH_OBSERVER(ScreenshotGrabberObserver, observers_,
-                    OnScreenshotCompleted(screenshot_result, screenshot_path));
+  for (ScreenshotGrabberObserver& observer : observers_)
+    observer.OnScreenshotCompleted(screenshot_result, screenshot_path);
 }
 
 void ScreenshotGrabber::AddObserver(ScreenshotGrabberObserver* observer) {
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index a358128..58fa126 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -183,12 +183,7 @@
 
   if (!base::FeatureList::IsEnabled(kToolkitViewsScrollWithLayers))
     return;
-
-  background_color_ = SK_ColorWHITE;
-  contents_viewport_->set_background(
-      Background::CreateSolidBackground(background_color_));
-  contents_viewport_->SetPaintToLayer(true);
-  contents_viewport_->layer()->SetMasksToBounds(true);
+  EnableViewPortLayer();
 }
 
 ScrollView::~ScrollView() {
@@ -699,6 +694,14 @@
   return contents_viewport_->layer() != nullptr;
 }
 
+void ScrollView::EnableViewPortLayer() {
+  background_color_ = SK_ColorWHITE;
+  contents_viewport_->set_background(
+      Background::CreateSolidBackground(background_color_));
+  contents_viewport_->SetPaintToLayer(true);
+  contents_viewport_->layer()->SetMasksToBounds(true);
+}
+
 void ScrollView::OnLayerScrolled() {
   UpdateScrollBarPositions();
   ScrollHeader();
diff --git a/ui/views/controls/scroll_view.h b/ui/views/controls/scroll_view.h
index ff444bb..8e30b169 100644
--- a/ui/views/controls/scroll_view.h
+++ b/ui/views/controls/scroll_view.h
@@ -109,6 +109,10 @@
                          bool is_page,
                          bool is_positive) override;
 
+  // TODO(djacobo): Remove this method when http://crbug.com/656198  is closed.
+  // Force |contents_viewport_| to enable a Layer().
+  void EnableViewPortLayer();
+
  private:
   friend class test::ScrollViewTestApi;