diff --git a/chrome/browser/devtools/device/adb/adb_client_socket.cc b/chrome/browser/devtools/device/adb/adb_client_socket.cc
index 98da348d..bd9f447 100644
--- a/chrome/browser/devtools/device/adb/adb_client_socket.cc
+++ b/chrome/browser/devtools/device/adb/adb_client_socket.cc
@@ -212,7 +212,7 @@
           destination: LOCAL
         }
         policy {
-          cookies_allowed: false
+          cookies_allowed: NO
           setting:
             "To use adb with a device connected over USB, you must enable USB "
             "debugging in the device system settings, under Developer options."
diff --git a/chrome/browser/search_engines/ui_thread_search_terms_data.cc b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
index a25f3fed..d80a711 100644
--- a/chrome/browser/search_engines/ui_thread_search_terms_data.cc
+++ b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
@@ -68,15 +68,11 @@
       BrowserThread::CurrentlyOn(BrowserThread::UI));
   base::string16 rlz_string;
 #if BUILDFLAG(ENABLE_RLZ)
-  static std::string* brand = []() {
-    auto* extracted = new std::string();
-    if (!google_brand::GetBrand(extracted))
-      extracted->clear();
-    return extracted;
-  }();
   // For organic brandcodes do not use rlz at all. Empty brandcode usually
   // means a chromium install. This is ok.
-  if (!brand->empty() && !google_brand::IsOrganic(*brand)) {
+  std::string brand;
+  if (google_brand::GetBrand(&brand) && !brand.empty() &&
+      !google_brand::IsOrganic(brand)) {
     // This call will return false the first time(s) it is called until the
     // value has been cached. This normally would mean that at most one omnibox
     // search might not send the RLZ data but this is not really a problem.
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 07e4c001..d64d6e9 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -899,8 +899,6 @@
                         OnJavaScriptExecuteResponse)
     IPC_MESSAGE_HANDLER(FrameHostMsg_VisualStateResponse,
                         OnVisualStateResponse)
-    IPC_MESSAGE_HANDLER(FrameHostMsg_SmartClipDataExtracted,
-                        OnSmartClipDataExtracted)
     IPC_MESSAGE_HANDLER_DELAY_REPLY(FrameHostMsg_RunJavaScriptDialog,
                                     OnRunJavaScriptDialog)
     IPC_MESSAGE_HANDLER_DELAY_REPLY(FrameHostMsg_RunBeforeUnloadConfirm,
@@ -1935,11 +1933,17 @@
   for (const auto& iter : ax_tree_snapshot_callbacks_)
     iter.second.Run(ui::AXTreeUpdate());
 
-  for (const auto& iter : smart_clip_callbacks_)
-    iter.second.Run(base::string16(), base::string16());
+#if defined(OS_ANDROID)
+  // Execute any pending Samsung smart clip callbacks.
+  for (base::IDMap<std::unique_ptr<ExtractSmartClipDataCallback>>::iterator
+           iter(&smart_clip_callbacks_);
+       !iter.IsAtEnd(); iter.Advance()) {
+    std::move(*iter.GetCurrentValue()).Run(base::string16(), base::string16());
+  }
+  smart_clip_callbacks_.Clear();
+#endif  // defined(OS_ANDROID)
 
   ax_tree_snapshot_callbacks_.clear();
-  smart_clip_callbacks_.clear();
   javascript_callbacks_.clear();
   visual_state_callbacks_.clear();
 
@@ -2046,25 +2050,24 @@
   }
 }
 
-void RenderFrameHostImpl::RequestSmartClipExtract(SmartClipCallback callback,
-                                                  gfx::Rect rect) {
-  static uint32_t next_id = 1;
-  uint32_t key = next_id++;
-  Send(new FrameMsg_ExtractSmartClipData(routing_id_, key, rect));
-  smart_clip_callbacks_.insert(std::make_pair(key, callback));
+#if defined(OS_ANDROID)
+void RenderFrameHostImpl::RequestSmartClipExtract(
+    ExtractSmartClipDataCallback callback,
+    gfx::Rect rect) {
+  int32_t callback_id = smart_clip_callbacks_.Add(
+      std::make_unique<ExtractSmartClipDataCallback>(std::move(callback)));
+  frame_->ExtractSmartClipData(
+      rect, base::BindOnce(&RenderFrameHostImpl::OnSmartClipDataExtracted,
+                           base::Unretained(this), callback_id));
 }
 
-void RenderFrameHostImpl::OnSmartClipDataExtracted(uint32_t id,
-                                                   base::string16 text,
-                                                   base::string16 html) {
-  auto it = smart_clip_callbacks_.find(id);
-  if (it != smart_clip_callbacks_.end()) {
-    it->second.Run(text, html);
-    smart_clip_callbacks_.erase(it);
-  } else {
-    NOTREACHED() << "Received smartclip data response for unknown request";
-  }
+void RenderFrameHostImpl::OnSmartClipDataExtracted(int32_t callback_id,
+                                                   const base::string16& text,
+                                                   const base::string16& html) {
+  std::move(*smart_clip_callbacks_.Lookup(callback_id)).Run(text, html);
+  smart_clip_callbacks_.Remove(callback_id);
 }
+#endif  // defined(OS_ANDROID)
 
 void RenderFrameHostImpl::OnVisualStateResponse(uint64_t id) {
   auto it = visual_state_callbacks_.find(id);
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index f00ec001..6ab79b6fe 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -18,6 +18,7 @@
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
+#include "base/containers/id_map.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -146,8 +147,6 @@
   using AXTreeSnapshotCallback =
       base::Callback<void(
           const ui::AXTreeUpdate&)>;
-  using SmartClipCallback = base::Callback<void(const base::string16& text,
-                                                const base::string16& html)>;
 
   // An accessibility reset is only allowed to prevent very rare corner cases
   // or race conditions where the browser and renderer get out of sync. If
@@ -502,8 +501,18 @@
   // renderer process to change the accessibility mode.
   void UpdateAccessibilityMode();
 
+#if defined(OS_ANDROID)
   // Samsung Galaxy Note-specific "smart clip" stylus text getter.
-  void RequestSmartClipExtract(SmartClipCallback callback, gfx::Rect rect);
+  using ExtractSmartClipDataCallback =
+      base::OnceCallback<void(const base::string16&, const base::string16&)>;
+
+  void RequestSmartClipExtract(ExtractSmartClipDataCallback callback,
+                               gfx::Rect rect);
+
+  void OnSmartClipDataExtracted(int32_t callback_id,
+                                const base::string16& text,
+                                const base::string16& html);
+#endif  // defined(OS_ANDROID)
 
   // Request a one-time snapshot of the accessibility tree without changing
   // the accessibility mode.
@@ -832,9 +841,6 @@
   void OnAccessibilitySnapshotResponse(
       int callback_id,
       const AXContentTreeUpdate& snapshot);
-  void OnSmartClipDataExtracted(uint32_t id,
-                                base::string16 text,
-                                base::string16 html);
   void OnToggleFullscreen(bool enter_fullscreen);
   void OnSuddenTerminationDisablerChanged(
       bool present,
@@ -1250,7 +1256,10 @@
   std::map<int, AXTreeSnapshotCallback> ax_tree_snapshot_callbacks_;
 
   // Samsung Galaxy Note-specific "smart clip" stylus text getter.
-  std::map<uint32_t, SmartClipCallback> smart_clip_callbacks_;
+#if defined(OS_ANDROID)
+  base::IDMap<std::unique_ptr<ExtractSmartClipDataCallback>>
+      smart_clip_callbacks_;
+#endif  // defined(OS_ANDROID)
 
   // Callback when an event is received, for testing.
   base::Callback<void(RenderFrameHostImpl*, ui::AXEvent, int)>
diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc
index 074ad70..0b1f5c2 100644
--- a/content/browser/web_contents/web_contents_android.cc
+++ b/content/browser/web_contents/web_contents_android.cc
@@ -5,6 +5,8 @@
 #include "content/browser/web_contents/web_contents_android.h"
 
 #include <stdint.h>
+#include <string>
+#include <vector>
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
@@ -27,6 +29,7 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/web_contents/web_contents_view_android.h"
 #include "content/common/devtools_messages.h"
+#include "content/common/frame.mojom.h"
 #include "content/common/frame_messages.h"
 #include "content/common/input_messages.h"
 #include "content/common/view_messages.h"
@@ -558,11 +561,9 @@
   ScopedJavaGlobalRef<jobject> j_callback;
   j_callback.Reset(env, callback);
 
-  RenderFrameHostImpl::SmartClipCallback smart_clip_callback =
-      base::Bind(&SmartClipCallback, j_callback);
-
   web_contents_->GetMainFrame()->RequestSmartClipExtract(
-      smart_clip_callback, gfx::Rect(x, y, width, height));
+      base::BindOnce(&SmartClipCallback, j_callback),
+      gfx::Rect(x, y, width, height));
 }
 
 void WebContentsAndroid::RequestAccessibilitySnapshot(
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index b5589cf..08f9728c 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -10,12 +10,14 @@
 import "content/public/common/url_loader.mojom";
 import "content/public/common/window_container_type.mojom";
 import "mojo/common/unguessable_token.mojom";
+import "mojo/common/string16.mojom";
 import "services/service_manager/public/interfaces/interface_provider.mojom";
 import "third_party/WebKit/common/feature_policy/feature_policy.mojom";
 import "third_party/WebKit/public/platform/referrer.mojom";
 import "third_party/WebKit/public/web/window_features.mojom";
 import "ui/base/mojo/window_open_disposition.mojom";
 import "url/mojo/url.mojom";
+import "ui/gfx/geometry/mojo/geometry.mojom";
 
 // The name of the InterfaceProviderSpec in service manifests used by the
 // frame tree to expose frame-specific interfaces between renderer and browser.
@@ -25,6 +27,12 @@
 interface Frame {
   GetInterfaceProvider(service_manager.mojom.InterfaceProvider& interfaces);
   GetCanonicalUrlForSharing() => (url.mojom.Url? canonical_url);
+
+  // Samsung Galaxy Note-specific "smart clip" stylus text getter.
+  // Extracts the data at the given rect.
+  // TODO(crbug.com/676224): Only enable for OS_ANDROID.
+  ExtractSmartClipData(gfx.mojom.Rect rect)
+      => (mojo.common.mojom.String16 text, mojo.common.mojom.String16 html);
 };
 
 // See src/content/common/navigation_params.h
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 4628f0a..8ba6261 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -834,12 +834,6 @@
 IPC_MESSAGE_ROUTED1(FrameMsg_TextSurroundingSelectionRequest,
                     uint32_t /* max_length */)
 
-// Extracts the data at the given rect, returning it through the
-// SmartClipDataExtracted IPC.
-IPC_MESSAGE_ROUTED2(FrameMsg_ExtractSmartClipData,
-                    uint32_t /* id */,
-                    gfx::Rect /* rect */)
-
 // Change the accessibility mode in the renderer process.
 IPC_MESSAGE_ROUTED1(FrameMsg_SetAccessibilityMode, ui::AXMode)
 
@@ -1554,12 +1548,6 @@
 // The message is delivered using RenderWidget::QueueMessage.
 IPC_MESSAGE_ROUTED1(FrameHostMsg_VisualStateResponse, uint64_t /* id */)
 
-// Reply to the ExtractSmartClipData message.
-IPC_MESSAGE_ROUTED3(FrameHostMsg_SmartClipDataExtracted,
-                    uint32_t /* id */,
-                    base::string16 /* text */,
-                    base::string16 /* html */)
-
 // Puts the browser into "tab fullscreen" mode for the sending renderer.
 // See the comment in chrome/browser/ui/browser.h for more details.
 IPC_MESSAGE_ROUTED1(FrameHostMsg_ToggleFullscreen, bool /* enter_fullscreen */)
diff --git a/content/renderer/loader/url_loader_client_impl.cc b/content/renderer/loader/url_loader_client_impl.cc
index a38faf48..e2d628e 100644
--- a/content/renderer/loader/url_loader_client_impl.cc
+++ b/content/renderer/loader/url_loader_client_impl.cc
@@ -4,6 +4,8 @@
 
 #include "content/renderer/loader/url_loader_client_impl.h"
 
+#include <iterator>
+
 #include "base/callback.h"
 #include "base/single_thread_task_runner.h"
 #include "content/common/resource_messages.h"
@@ -13,6 +15,112 @@
 
 namespace content {
 
+class URLLoaderClientImpl::DeferredMessage {
+ public:
+  DeferredMessage() = default;
+  virtual void HandleMessage(ResourceDispatcher* dispatcher,
+                             int request_id) = 0;
+  virtual bool IsCompletionMessage() const = 0;
+  virtual ~DeferredMessage() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeferredMessage);
+};
+
+class URLLoaderClientImpl::DeferredOnReceiveResponse final
+    : public DeferredMessage {
+ public:
+  explicit DeferredOnReceiveResponse(const ResourceResponseHead& response_head)
+      : response_head_(response_head) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnReceivedResponse(request_id, response_head_);
+  }
+  bool IsCompletionMessage() const override { return false; }
+
+ private:
+  const ResourceResponseHead response_head_;
+};
+
+class URLLoaderClientImpl::DeferredOnReceiveRedirect final
+    : public DeferredMessage {
+ public:
+  DeferredOnReceiveRedirect(const net::RedirectInfo& redirect_info,
+                            const ResourceResponseHead& response_head)
+      : redirect_info_(redirect_info), response_head_(response_head) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnReceivedRedirect(request_id, redirect_info_, response_head_);
+  }
+  bool IsCompletionMessage() const override { return false; }
+
+ private:
+  const net::RedirectInfo redirect_info_;
+  const ResourceResponseHead response_head_;
+};
+
+class URLLoaderClientImpl::DeferredOnDataDownloaded final
+    : public DeferredMessage {
+ public:
+  DeferredOnDataDownloaded(int64_t data_length, int64_t encoded_data_length)
+      : data_length_(data_length), encoded_data_length_(encoded_data_length) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnDownloadedData(request_id, data_length_,
+                                 encoded_data_length_);
+  }
+  bool IsCompletionMessage() const override { return false; }
+
+ private:
+  const int64_t data_length_;
+  const int64_t encoded_data_length_;
+};
+
+class URLLoaderClientImpl::DeferredOnUploadProgress final
+    : public DeferredMessage {
+ public:
+  DeferredOnUploadProgress(int64_t current, int64_t total)
+      : current_(current), total_(total) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnDownloadedData(request_id, current_, total_);
+  }
+  bool IsCompletionMessage() const override { return false; }
+
+ private:
+  const int64_t current_;
+  const int64_t total_;
+};
+
+class URLLoaderClientImpl::DeferredOnReceiveCachedMetadata final
+    : public DeferredMessage {
+ public:
+  explicit DeferredOnReceiveCachedMetadata(const std::vector<uint8_t>& data)
+      : data_(data) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnReceivedCachedMetadata(request_id, data_);
+  }
+  bool IsCompletionMessage() const override { return false; }
+
+ private:
+  const std::vector<uint8_t> data_;
+};
+
+class URLLoaderClientImpl::DeferredOnComplete final : public DeferredMessage {
+ public:
+  explicit DeferredOnComplete(const network::URLLoaderCompletionStatus& status)
+      : status_(status) {}
+
+  void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
+    dispatcher->OnRequestComplete(request_id, status_);
+  }
+  bool IsCompletionMessage() const override { return true; }
+
+ private:
+  const network::URLLoaderCompletionStatus status_;
+};
+
 URLLoaderClientImpl::URLLoaderClientImpl(
     int request_id,
     ResourceDispatcher* resource_dispatcher,
@@ -40,7 +148,7 @@
 
 void URLLoaderClientImpl::FlushDeferredMessages() {
   DCHECK(!is_deferred_);
-  std::vector<IPC::Message> messages;
+  std::vector<std::unique_ptr<DeferredMessage>> messages;
   messages.swap(deferred_messages_);
   bool has_completion_message = false;
   base::WeakPtr<URLLoaderClientImpl> weak_this = weak_factory_.GetWeakPtr();
@@ -49,19 +157,22 @@
   //  - transfer size change (dispatched later)
   //  - completion (dispatched by |body_consumer_| or dispatched later)
   for (size_t index = 0; index < messages.size(); ++index) {
-    if (messages[index].type() == ResourceMsg_RequestComplete::ID) {
+    if (messages[index]->IsCompletionMessage()) {
       // The completion message arrives at the end of the message queue.
       DCHECK(!has_completion_message);
       DCHECK_EQ(index, messages.size() - 1);
       has_completion_message = true;
       break;
     }
-    resource_dispatcher_->DispatchMessage(messages[index]);
+
+    messages[index]->HandleMessage(resource_dispatcher_, request_id_);
     if (!weak_this)
       return;
     if (is_deferred_) {
-      deferred_messages_.insert(deferred_messages_.begin(),
-                                messages.begin() + index + 1, messages.end());
+      deferred_messages_.insert(
+          deferred_messages_.begin(),
+          std::make_move_iterator(messages.begin()) + index + 1,
+          std::make_move_iterator(messages.end()));
       return;
     }
   }
@@ -77,8 +188,7 @@
     if (is_deferred_) {
       if (has_completion_message) {
         DCHECK_GT(messages.size(), 0u);
-        DCHECK_EQ(messages.back().type(),
-                  static_cast<uint32_t>(ResourceMsg_RequestComplete::ID));
+        DCHECK(messages.back()->IsCompletionMessage());
         deferred_messages_.emplace_back(std::move(messages.back()));
       }
       return;
@@ -97,10 +207,8 @@
   // Dispatch the completion message.
   if (has_completion_message) {
     DCHECK_GT(messages.size(), 0u);
-    DCHECK_EQ(messages.back().type(),
-              static_cast<uint32_t>(ResourceMsg_RequestComplete::ID));
-
-    resource_dispatcher_->DispatchMessage(messages.back());
+    DCHECK(messages.back()->IsCompletionMessage());
+    messages.back()->HandleMessage(resource_dispatcher_, request_id_);
   }
 }
 
@@ -117,10 +225,12 @@
     mojom::DownloadedTempFilePtr downloaded_file) {
   has_received_response_ = true;
   downloaded_file_ = std::move(downloaded_file);
-  if (NeedsStoringMessage())
-    StoreAndDispatch(ResourceMsg_ReceivedResponse(request_id_, response_head));
-  else
+  if (NeedsStoringMessage()) {
+    StoreAndDispatch(
+        std::make_unique<DeferredOnReceiveResponse>(response_head));
+  } else {
     resource_dispatcher_->OnReceivedResponse(request_id_, response_head);
+  }
 }
 
 void URLLoaderClientImpl::OnReceiveRedirect(
@@ -129,8 +239,8 @@
   DCHECK(!has_received_response_);
   DCHECK(!body_consumer_);
   if (NeedsStoringMessage()) {
-    StoreAndDispatch(ResourceMsg_ReceivedRedirect(request_id_, redirect_info,
-                                                  response_head));
+    StoreAndDispatch(std::make_unique<DeferredOnReceiveRedirect>(
+        redirect_info, response_head));
   } else {
     resource_dispatcher_->OnReceivedRedirect(request_id_, redirect_info,
                                              response_head);
@@ -141,17 +251,31 @@
                                            int64_t encoded_data_len) {
   if (NeedsStoringMessage()) {
     StoreAndDispatch(
-        ResourceMsg_DataDownloaded(request_id_, data_len, encoded_data_len));
+        std::make_unique<DeferredOnDataDownloaded>(data_len, encoded_data_len));
   } else {
     resource_dispatcher_->OnDownloadedData(request_id_, data_len,
                                            encoded_data_len);
   }
 }
 
+void URLLoaderClientImpl::OnUploadProgress(
+    int64_t current_position,
+    int64_t total_size,
+    OnUploadProgressCallback ack_callback) {
+  if (NeedsStoringMessage()) {
+    StoreAndDispatch(std::make_unique<DeferredOnUploadProgress>(
+        current_position, total_size));
+  } else {
+    resource_dispatcher_->OnUploadProgress(request_id_, current_position,
+                                           total_size);
+  }
+  std::move(ack_callback).Run();
+}
+
 void URLLoaderClientImpl::OnReceiveCachedMetadata(
     const std::vector<uint8_t>& data) {
   if (NeedsStoringMessage()) {
-    StoreAndDispatch(ResourceMsg_ReceivedCachedMetadata(request_id_, data));
+    StoreAndDispatch(std::make_unique<DeferredOnReceiveCachedMetadata>(data));
   } else {
     resource_dispatcher_->OnReceivedCachedMetadata(request_id_, data);
   }
@@ -186,7 +310,7 @@
   has_received_complete_ = true;
   if (!body_consumer_) {
     if (NeedsStoringMessage()) {
-      StoreAndDispatch(ResourceMsg_RequestComplete(request_id_, status));
+      StoreAndDispatch(std::make_unique<DeferredOnComplete>(status));
     } else {
       resource_dispatcher_->OnRequestComplete(request_id_, status);
     }
@@ -199,32 +323,19 @@
   return is_deferred_ || deferred_messages_.size() > 0;
 }
 
-void URLLoaderClientImpl::StoreAndDispatch(const IPC::Message& message) {
+void URLLoaderClientImpl::StoreAndDispatch(
+    std::unique_ptr<DeferredMessage> message) {
   DCHECK(NeedsStoringMessage());
   if (is_deferred_) {
-    deferred_messages_.push_back(message);
+    deferred_messages_.push_back(std::move(message));
   } else if (deferred_messages_.size() > 0) {
-    deferred_messages_.push_back(message);
+    deferred_messages_.push_back(std::move(message));
     FlushDeferredMessages();
   } else {
     NOTREACHED();
   }
 }
 
-void URLLoaderClientImpl::OnUploadProgress(
-    int64_t current_position,
-    int64_t total_size,
-    OnUploadProgressCallback ack_callback) {
-  if (NeedsStoringMessage()) {
-    StoreAndDispatch(
-        ResourceMsg_UploadProgress(request_id_, current_position, total_size));
-  } else {
-    resource_dispatcher_->OnUploadProgress(request_id_, current_position,
-                                           total_size);
-  }
-  std::move(ack_callback).Run();
-}
-
 void URLLoaderClientImpl::OnConnectionClosed() {
   // If the connection aborts before the load completes, mark it as aborted.
   if (!has_received_complete_) {
diff --git a/content/renderer/loader/url_loader_client_impl.h b/content/renderer/loader/url_loader_client_impl.h
index 7497bbe..26ce1d2 100644
--- a/content/renderer/loader/url_loader_client_impl.h
+++ b/content/renderer/loader/url_loader_client_impl.h
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "content/common/content_export.h"
 #include "content/public/common/url_loader.mojom.h"
-#include "ipc/ipc_message.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 
@@ -75,13 +74,21 @@
   void OnComplete(const network::URLLoaderCompletionStatus& status) override;
 
  private:
+  class DeferredMessage;
+  class DeferredOnReceiveResponse;
+  class DeferredOnReceiveRedirect;
+  class DeferredOnDataDownloaded;
+  class DeferredOnUploadProgress;
+  class DeferredOnReceiveCachedMetadata;
+  class DeferredOnComplete;
+
   bool NeedsStoringMessage() const;
-  void StoreAndDispatch(const IPC::Message& message);
+  void StoreAndDispatch(std::unique_ptr<DeferredMessage> message);
   void OnConnectionClosed();
 
   scoped_refptr<URLResponseBodyConsumer> body_consumer_;
   mojom::DownloadedTempFilePtr downloaded_file_;
-  std::vector<IPC::Message> deferred_messages_;
+  std::vector<std::unique_ptr<DeferredMessage>> deferred_messages_;
   const int request_id_;
   bool has_received_response_ = false;
   bool has_received_complete_ = false;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 5c03598c8..7e5d0a19 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1777,7 +1777,6 @@
                         OnSetAccessibilityMode)
     IPC_MESSAGE_HANDLER(AccessibilityMsg_SnapshotTree,
                         OnSnapshotAccessibilityTree)
-    IPC_MESSAGE_HANDLER(FrameMsg_ExtractSmartClipData, OnExtractSmartClipData)
     IPC_MESSAGE_HANDLER(FrameMsg_UpdateOpener, OnUpdateOpener)
     IPC_MESSAGE_HANDLER(FrameMsg_DidUpdateFramePolicy, OnDidUpdateFramePolicy)
     IPC_MESSAGE_HANDLER(FrameMsg_SetFrameOwnerProperties,
@@ -2407,13 +2406,15 @@
       routing_id_, callback_id, response));
 }
 
-void RenderFrameImpl::OnExtractSmartClipData(uint32_t id,
-                                             const gfx::Rect& rect) {
+void RenderFrameImpl::ExtractSmartClipData(
+    const gfx::Rect& rect,
+    ExtractSmartClipDataCallback callback) {
+#if defined(OS_ANDROID)
   blink::WebString clip_text;
   blink::WebString clip_html;
   GetWebFrame()->ExtractSmartClipData(rect, clip_text, clip_html);
-  Send(new FrameHostMsg_SmartClipDataExtracted(
-      routing_id_, id, clip_text.Utf16(), clip_html.Utf16()));
+  std::move(callback).Run(clip_text.Utf16(), clip_html.Utf16());
+#endif  // defined(OS_ANDROID)
 }
 
 void RenderFrameImpl::OnUpdateOpener(int opener_routing_id) {
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index a96be169..85d16b2 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -515,6 +515,9 @@
       service_manager::mojom::InterfaceProviderRequest request) override;
   void GetCanonicalUrlForSharing(
       GetCanonicalUrlForSharingCallback callback) override;
+  void ExtractSmartClipData(
+      const gfx::Rect& rect,
+      const ExtractSmartClipDataCallback callback) override;
 
   // mojom::FrameBindingsControl implementation:
   void AllowBindings(int32_t enabled_bindings_flags) override;
@@ -1028,7 +1031,6 @@
   void OnTextSurroundingSelectionRequest(uint32_t max_length);
   void OnSetAccessibilityMode(ui::AXMode new_mode);
   void OnSnapshotAccessibilityTree(int callback_id);
-  void OnExtractSmartClipData(uint32_t callback_id, const gfx::Rect& rect);
   void OnUpdateOpener(int opener_routing_id);
   void OnDidUpdateFramePolicy(const blink::FramePolicy& frame_policy);
   void OnSetFrameOwnerProperties(
diff --git a/media/mojo/clients/mojo_audio_decoder.cc b/media/mojo/clients/mojo_audio_decoder.cc
index 94a86b3..3bfc626 100644
--- a/media/mojo/clients/mojo_audio_decoder.cc
+++ b/media/mojo/clients/mojo_audio_decoder.cc
@@ -24,6 +24,8 @@
     mojom::AudioDecoderPtr remote_decoder)
     : task_runner_(task_runner),
       remote_decoder_info_(remote_decoder.PassInterface()),
+      writer_capacity_(
+          GetDefaultDecoderBufferConverterCapacity(DemuxerStream::AUDIO)),
       client_binding_(this) {
   DVLOG(1) << __func__;
 }
@@ -179,7 +181,8 @@
   if (success && !mojo_decoder_buffer_writer_) {
     mojo::ScopedDataPipeConsumerHandle remote_consumer_handle;
     mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create(
-        DemuxerStream::AUDIO, &remote_consumer_handle);
+        writer_capacity_, &remote_consumer_handle);
+
     // Pass consumer end to |remote_decoder_|.
     remote_decoder_->SetDataSource(std::move(remote_consumer_handle));
   }
diff --git a/media/mojo/clients/mojo_audio_decoder.h b/media/mojo/clients/mojo_audio_decoder.h
index 11dc76e..8b23e33 100644
--- a/media/mojo/clients/mojo_audio_decoder.h
+++ b/media/mojo/clients/mojo_audio_decoder.h
@@ -44,6 +44,10 @@
   // AudioDecoderClient implementation.
   void OnBufferDecoded(mojom::AudioBufferPtr buffer) final;
 
+  void set_writer_capacity_for_testing(uint32_t capacity) {
+    writer_capacity_ = capacity;
+  }
+
  private:
   void BindRemoteDecoder();
 
@@ -71,6 +75,8 @@
 
   std::unique_ptr<MojoDecoderBufferWriter> mojo_decoder_buffer_writer_;
 
+  uint32_t writer_capacity_ = 0;
+
   // Binding for AudioDecoderClient, bound to the |task_runner_|.
   mojo::AssociatedBinding<AudioDecoderClient> client_binding_;
 
diff --git a/media/mojo/clients/mojo_audio_decoder_unittest.cc b/media/mojo/clients/mojo_audio_decoder_unittest.cc
index 8f8ba11..0a49100 100644
--- a/media/mojo/clients/mojo_audio_decoder_unittest.cc
+++ b/media/mojo/clients/mojo_audio_decoder_unittest.cc
@@ -123,6 +123,10 @@
         std::move(request));
   }
 
+  void SetWriterCapacity(uint32_t capacity) {
+    mojo_audio_decoder_->set_writer_capacity_for_testing(capacity);
+  }
+
   void InitializeAndExpect(bool success) {
     DVLOG(1) << __func__ << ": success=" << success;
     EXPECT_CALL(*this, OnInitialized(success))
@@ -259,7 +263,13 @@
 
 TEST_F(MojoAudioDecoderTest, Reset_DuringDecode) {
   Initialize();
+  DecodeAndReset();
+}
 
+TEST_F(MojoAudioDecoderTest, Reset_DuringDecode_ChunkedWrite) {
+  // Use a small writer capacity to force chunked write.
+  SetWriterCapacity(10);
+  Initialize();
   DecodeAndReset();
 }
 
diff --git a/media/mojo/clients/mojo_decryptor.cc b/media/mojo/clients/mojo_decryptor.cc
index b9da44ae..9a56cb9 100644
--- a/media/mojo/clients/mojo_decryptor.cc
+++ b/media/mojo/clients/mojo_decryptor.cc
@@ -33,26 +33,48 @@
 
 }  // namespace
 
-MojoDecryptor::MojoDecryptor(mojom::DecryptorPtr remote_decryptor)
+// TODO(xhwang): Consider adding an Initialize() to reduce the amount of work
+// done in the constructor.
+MojoDecryptor::MojoDecryptor(mojom::DecryptorPtr remote_decryptor,
+                             uint32_t writer_capacity)
     : remote_decryptor_(std::move(remote_decryptor)), weak_factory_(this) {
   DVLOG(1) << __func__;
 
-  // Allocate DataPipe size based on video content.
+  uint32_t audio_writer_capacity =
+      writer_capacity
+          ? writer_capacity
+          : GetDefaultDecoderBufferConverterCapacity(DemuxerStream::AUDIO);
+  uint32_t video_writer_capacity =
+      writer_capacity
+          ? writer_capacity
+          : GetDefaultDecoderBufferConverterCapacity(DemuxerStream::VIDEO);
 
-  mojo::ScopedDataPipeConsumerHandle remote_consumer_handle;
-  mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create(
-      DemuxerStream::VIDEO, &remote_consumer_handle);
+  mojo::ScopedDataPipeConsumerHandle audio_consumer_handle;
+  audio_buffer_writer_ = MojoDecoderBufferWriter::Create(
+      audio_writer_capacity, &audio_consumer_handle);
 
-  mojo::ScopedDataPipeProducerHandle remote_producer_handle;
-  mojo_decoder_buffer_reader_ = MojoDecoderBufferReader::Create(
-      DemuxerStream::VIDEO, &remote_producer_handle);
+  mojo::ScopedDataPipeConsumerHandle video_consumer_handle;
+  video_buffer_writer_ = MojoDecoderBufferWriter::Create(
+      video_writer_capacity, &video_consumer_handle);
+
+  mojo::ScopedDataPipeConsumerHandle decrypt_consumer_handle;
+  // Allocate decrypt-only DataPipe size based on video content.
+  decrypt_buffer_writer_ = MojoDecoderBufferWriter::Create(
+      video_writer_capacity, &decrypt_consumer_handle);
+
+  mojo::ScopedDataPipeProducerHandle decrypted_producer_handle;
+  // Allocate decrypt-only DataPipe size based on video content.
+  decrypted_buffer_reader_ = MojoDecoderBufferReader::Create(
+      GetDefaultDecoderBufferConverterCapacity(DemuxerStream::VIDEO),
+      &decrypted_producer_handle);
 
   remote_decryptor_.set_connection_error_with_reason_handler(
       base::Bind(&MojoDecryptor::OnConnectionError, base::Unretained(this)));
 
   // Pass the other end of each pipe to |remote_decryptor_|.
-  remote_decryptor_->Initialize(std::move(remote_consumer_handle),
-                                std::move(remote_producer_handle));
+  remote_decryptor_->Initialize(
+      std::move(audio_consumer_handle), std::move(video_consumer_handle),
+      std::move(decrypt_consumer_handle), std::move(decrypted_producer_handle));
 }
 
 MojoDecryptor::~MojoDecryptor() {
@@ -82,7 +104,7 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   mojom::DecoderBufferPtr mojo_buffer =
-      mojo_decoder_buffer_writer_->WriteDecoderBuffer(encrypted);
+      decrypt_buffer_writer_->WriteDecoderBuffer(encrypted);
   if (!mojo_buffer) {
     decrypt_cb.Run(kError, nullptr);
     return;
@@ -123,11 +145,11 @@
 void MojoDecryptor::DecryptAndDecodeAudio(
     const scoped_refptr<DecoderBuffer>& encrypted,
     const AudioDecodeCB& audio_decode_cb) {
-  DVLOG(3) << __func__;
+  DVLOG(3) << __func__ << ": " << encrypted->AsHumanReadableString();
   DCHECK(thread_checker_.CalledOnValidThread());
 
   mojom::DecoderBufferPtr mojo_buffer =
-      mojo_decoder_buffer_writer_->WriteDecoderBuffer(encrypted);
+      audio_buffer_writer_->WriteDecoderBuffer(encrypted);
   if (!mojo_buffer) {
     audio_decode_cb.Run(kError, AudioFrames());
     return;
@@ -143,11 +165,11 @@
 void MojoDecryptor::DecryptAndDecodeVideo(
     const scoped_refptr<DecoderBuffer>& encrypted,
     const VideoDecodeCB& video_decode_cb) {
-  DVLOG(3) << __func__;
+  DVLOG(3) << __func__ << ": " << encrypted->AsHumanReadableString();
   DCHECK(thread_checker_.CalledOnValidThread());
 
   mojom::DecoderBufferPtr mojo_buffer =
-      mojo_decoder_buffer_writer_->WriteDecoderBuffer(encrypted);
+      video_buffer_writer_->WriteDecoderBuffer(encrypted);
   if (!mojo_buffer) {
     video_decode_cb.Run(kError, nullptr);
     return;
@@ -161,7 +183,7 @@
 }
 
 void MojoDecryptor::ResetDecoder(StreamType stream_type) {
-  DVLOG(1) << __func__;
+  DVLOG(1) << __func__ << ": stream_type = " << stream_type;
   DCHECK(thread_checker_.CalledOnValidThread());
 
   remote_decryptor_->ResetDecoder(stream_type);
@@ -197,7 +219,7 @@
     return;
   }
 
-  mojo_decoder_buffer_reader_->ReadDecoderBuffer(
+  decrypted_buffer_reader_->ReadDecoderBuffer(
       std::move(buffer),
       base::BindOnce(&MojoDecryptor::OnBufferRead, weak_factory_.GetWeakPtr(),
                      std::move(decrypt_cb), status));
diff --git a/media/mojo/clients/mojo_decryptor.h b/media/mojo/clients/mojo_decryptor.h
index d90d4b4..7ddd060 100644
--- a/media/mojo/clients/mojo_decryptor.h
+++ b/media/mojo/clients/mojo_decryptor.h
@@ -26,7 +26,10 @@
 // lives on the first time it is used in this class.
 class MojoDecryptor : public Decryptor {
  public:
-  explicit MojoDecryptor(mojom::DecryptorPtr remote_decryptor);
+  // |writer_capacity| can be used for testing. If 0, default writer capacity
+  // will be used.
+  MojoDecryptor(mojom::DecryptorPtr remote_decryptor,
+                uint32_t writer_capacity = 0);
   ~MojoDecryptor() final;
 
   // Decryptor implementation.
@@ -80,17 +83,22 @@
   void OnConnectionError(uint32_t custom_reason,
                          const std::string& description);
 
+  // Helper class to get the correct MojoDecoderBufferWriter;
+  MojoDecoderBufferWriter* GetWriter(StreamType stream_type);
+
   base::ThreadChecker thread_checker_;
 
   mojom::DecryptorPtr remote_decryptor_;
 
-  // Helper class to send DecoderBuffer to the |remote_decryptor_| for decrypt
-  // or decrypt-and-decode.
-  std::unique_ptr<MojoDecoderBufferWriter> mojo_decoder_buffer_writer_;
+  // Helper class to send DecoderBuffer to the |remote_decryptor_| for
+  // DecryptAndDecodeAudio(), DecryptAndDecodeVideo() and Decrypt().
+  std::unique_ptr<MojoDecoderBufferWriter> audio_buffer_writer_;
+  std::unique_ptr<MojoDecoderBufferWriter> video_buffer_writer_;
+  std::unique_ptr<MojoDecoderBufferWriter> decrypt_buffer_writer_;
 
   // Helper class to receive decrypted DecoderBuffer from the
-  // |remote_decryptor_|.
-  std::unique_ptr<MojoDecoderBufferReader> mojo_decoder_buffer_reader_;
+  // |remote_decryptor_|, shared by audio and video.
+  std::unique_ptr<MojoDecoderBufferReader> decrypted_buffer_reader_;
 
   NewKeyCB new_audio_key_cb_;
   NewKeyCB new_video_key_cb_;
diff --git a/media/mojo/clients/mojo_decryptor_unittest.cc b/media/mojo/clients/mojo_decryptor_unittest.cc
index 179741b3..d4e570f 100644
--- a/media/mojo/clients/mojo_decryptor_unittest.cc
+++ b/media/mojo/clients/mojo_decryptor_unittest.cc
@@ -35,7 +35,12 @@
 
 class MojoDecryptorTest : public ::testing::Test {
  public:
-  MojoDecryptorTest() {
+  MojoDecryptorTest() = default;
+  ~MojoDecryptorTest() override = default;
+
+  void SetWriterCapacity(uint32_t capacity) { writer_capacity_ = capacity; }
+
+  void Initialize() {
     decryptor_.reset(new StrictMock<MockDecryptor>());
 
     mojom::DecryptorPtr remote_decryptor;
@@ -44,11 +49,10 @@
         base::Bind(&MojoDecryptorTest::OnConnectionClosed,
                    base::Unretained(this))));
 
-    mojo_decryptor_.reset(new MojoDecryptor(std::move(remote_decryptor)));
+    mojo_decryptor_.reset(
+        new MojoDecryptor(std::move(remote_decryptor), writer_capacity_));
   }
 
-  ~MojoDecryptorTest() override = default;
-
   void DestroyClient() {
     EXPECT_CALL(*this, OnConnectionClosed());
     mojo_decryptor_.reset();
@@ -108,6 +112,8 @@
   // Fixture members.
   base::TestMessageLoop message_loop_;
 
+  uint32_t writer_capacity_ = 0;
+
   // The MojoDecryptor that we are testing.
   std::unique_ptr<MojoDecryptor> mojo_decryptor_;
 
@@ -122,7 +128,32 @@
 };
 
 // DecryptAndDecodeAudio() and ResetDecoder(kAudio) immediately.
-TEST_F(MojoDecryptorTest, ResetDuringDecryptAndDecodeAudio) {
+TEST_F(MojoDecryptorTest, Reset_DuringDecryptAndDecode_Audio) {
+  Initialize();
+
+  {
+    // Make sure calls are made in order.
+    InSequence seq;
+    EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
+        .WillOnce(Invoke(this, &MojoDecryptorTest::ReturnAudioFrames));
+    EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kAudio));
+    // The returned status could be success or aborted.
+    EXPECT_CALL(*this, AudioDecoded(_, _));
+  }
+
+  scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(100));
+  mojo_decryptor_->DecryptAndDecodeAudio(
+      std::move(buffer),
+      base::Bind(&MojoDecryptorTest::AudioDecoded, base::Unretained(this)));
+  mojo_decryptor_->ResetDecoder(Decryptor::kAudio);
+  base::RunLoop().RunUntilIdle();
+}
+
+// DecryptAndDecodeAudio() and ResetDecoder(kAudio) immediately.
+TEST_F(MojoDecryptorTest, Reset_DuringDecryptAndDecode_Audio_ChunkedWrite) {
+  SetWriterCapacity(20);
+  Initialize();
+
   {
     // Make sure calls are made in order.
     InSequence seq;
@@ -142,7 +173,9 @@
 }
 
 // DecryptAndDecodeVideo() and ResetDecoder(kVideo) immediately.
-TEST_F(MojoDecryptorTest, ResetDuringDecryptAndDecodeVideo) {
+TEST_F(MojoDecryptorTest, Reset_DuringDecryptAndDecode_Video) {
+  Initialize();
+
   {
     // Make sure calls are made in order.
     InSequence seq;
@@ -163,7 +196,77 @@
   base::RunLoop().RunUntilIdle();
 }
 
+// DecryptAndDecodeVideo() and ResetDecoder(kVideo) immediately.
+TEST_F(MojoDecryptorTest, Reset_DuringDecryptAndDecode_Video_ChunkedWrite) {
+  SetWriterCapacity(20);
+  Initialize();
+
+  {
+    // Make sure calls are made in order.
+    InSequence seq;
+    EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
+        .WillOnce(
+            Invoke(this, &MojoDecryptorTest::ReturnSharedBufferVideoFrame));
+    EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kVideo));
+    // The returned status could be success or aborted.
+    EXPECT_CALL(*this, VideoDecoded(_, _));
+    EXPECT_CALL(*this, OnFrameDestroyed());
+  }
+
+  scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(100));
+  mojo_decryptor_->DecryptAndDecodeVideo(
+      std::move(buffer),
+      base::Bind(&MojoDecryptorTest::VideoDecoded, base::Unretained(this)));
+  mojo_decryptor_->ResetDecoder(Decryptor::kVideo);
+  base::RunLoop().RunUntilIdle();
+}
+
+// DecryptAndDecodeAudio(), DecryptAndDecodeVideo(), ResetDecoder(kAudio) and
+// ResetDecoder(kVideo).
+TEST_F(MojoDecryptorTest, Reset_DuringDecryptAndDecode_AudioAndVideo) {
+  // Only test chunked write as it's the most complex and error prone case.
+  SetWriterCapacity(20);
+  Initialize();
+
+  {
+    // Make sure calls are made in order.
+    InSequence seq;
+    EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
+        .WillOnce(Invoke(this, &MojoDecryptorTest::ReturnAudioFrames));
+    EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kAudio));
+  }
+
+  {
+    // Make sure calls are made in order.
+    InSequence seq;
+    EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _))
+        .WillOnce(
+            Invoke(this, &MojoDecryptorTest::ReturnSharedBufferVideoFrame));
+    EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kVideo));
+  }
+
+  // The returned status could be success or aborted, and we don't care about
+  // the order.
+  EXPECT_CALL(*this, AudioDecoded(_, _));
+  EXPECT_CALL(*this, VideoDecoded(_, _));
+  EXPECT_CALL(*this, OnFrameDestroyed());
+
+  scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(100));
+
+  mojo_decryptor_->DecryptAndDecodeAudio(
+      std::move(buffer),
+      base::Bind(&MojoDecryptorTest::AudioDecoded, base::Unretained(this)));
+  mojo_decryptor_->DecryptAndDecodeVideo(
+      std::move(buffer),
+      base::Bind(&MojoDecryptorTest::VideoDecoded, base::Unretained(this)));
+  mojo_decryptor_->ResetDecoder(Decryptor::kAudio);
+  mojo_decryptor_->ResetDecoder(Decryptor::kVideo);
+  base::RunLoop().RunUntilIdle();
+}
+
 TEST_F(MojoDecryptorTest, VideoDecodeFreesBuffer) {
+  Initialize();
+
   // Call DecryptAndDecodeVideo(). Once the callback VideoDecoded() completes,
   // the frame will be destroyed, and the buffer will be released.
   {
@@ -182,6 +285,8 @@
 }
 
 TEST_F(MojoDecryptorTest, VideoDecodeFreesMultipleBuffers) {
+  Initialize();
+
   // Call DecryptAndDecodeVideo() multiple times.
   const int TIMES = 5;
   EXPECT_CALL(*this, VideoDecoded(Decryptor::Status::kSuccess, NotNull()))
@@ -201,6 +306,8 @@
 }
 
 TEST_F(MojoDecryptorTest, VideoDecodeHoldThenFreeBuffers) {
+  Initialize();
+
   // Call DecryptAndDecodeVideo() twice. Hang on to the buffers returned,
   // and free them later.
   scoped_refptr<VideoFrame> saved_frame1;
@@ -233,6 +340,8 @@
 }
 
 TEST_F(MojoDecryptorTest, EOSBuffer) {
+  Initialize();
+
   // Call DecryptAndDecodeVideo(), but return an EOS frame (which has no frame
   // data, so no memory needs to be freed).
   EXPECT_CALL(*this, VideoDecoded(Decryptor::Status::kSuccess, NotNull()));
@@ -247,11 +356,13 @@
 }
 
 TEST_F(MojoDecryptorTest, DestroyClient) {
+  Initialize();
   DestroyClient();
   base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(MojoDecryptorTest, DestroyService) {
+  Initialize();
   DestroyService();
   base::RunLoop().RunUntilIdle();
 
diff --git a/media/mojo/clients/mojo_demuxer_stream_impl.cc b/media/mojo/clients/mojo_demuxer_stream_impl.cc
index e2043fa..56c0e56 100644
--- a/media/mojo/clients/mojo_demuxer_stream_impl.cc
+++ b/media/mojo/clients/mojo_demuxer_stream_impl.cc
@@ -45,8 +45,9 @@
   }
 
   mojo::ScopedDataPipeConsumerHandle remote_consumer_handle;
-  mojo_decoder_buffer_writer_ =
-      MojoDecoderBufferWriter::Create(stream_->type(), &remote_consumer_handle);
+  mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create(
+      GetDefaultDecoderBufferConverterCapacity(stream_->type()),
+      &remote_consumer_handle);
 
   std::move(callback).Run(stream_->type(), std::move(remote_consumer_handle),
                           audio_config, video_config);
diff --git a/media/mojo/clients/mojo_video_decoder.cc b/media/mojo/clients/mojo_video_decoder.cc
index 64d45a04d..5e4d06e0 100644
--- a/media/mojo/clients/mojo_video_decoder.cc
+++ b/media/mojo/clients/mojo_video_decoder.cc
@@ -80,6 +80,8 @@
     : task_runner_(task_runner),
       remote_decoder_info_(remote_decoder.PassInterface()),
       gpu_factories_(gpu_factories),
+      writer_capacity_(
+          GetDefaultDecoderBufferConverterCapacity(DemuxerStream::VIDEO)),
       client_binding_(this),
       media_log_service_(media_log),
       media_log_binding_(&media_log_service_),
@@ -275,10 +277,9 @@
       base::MakeRefCounted<MojoVideoFrameHandleReleaser>(
           std::move(video_frame_handle_releaser_ptr_info), task_runner_);
 
-  // Create |decoder_buffer_pipe|, and bind |mojo_decoder_buffer_writer_| to it.
   mojo::ScopedDataPipeConsumerHandle remote_consumer_handle;
   mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create(
-      DemuxerStream::VIDEO, &remote_consumer_handle);
+      writer_capacity_, &remote_consumer_handle);
 
   // Generate |command_buffer_id|.
   media::mojom::CommandBufferIdPtr command_buffer_id;
diff --git a/media/mojo/clients/mojo_video_decoder.h b/media/mojo/clients/mojo_video_decoder.h
index b3c96ca..9a957a9 100644
--- a/media/mojo/clients/mojo_video_decoder.h
+++ b/media/mojo/clients/mojo_video_decoder.h
@@ -61,6 +61,10 @@
       const base::Optional<base::UnguessableToken>& release_token) final;
   void RequestOverlayInfo(bool restart_for_transitions) final;
 
+  void set_writer_capacity_for_testing(uint32_t capacity) {
+    writer_capacity_ = capacity;
+  }
+
  private:
   void OnInitializeDone(bool status,
                         bool needs_bitstream_conversion,
@@ -96,6 +100,9 @@
 
   mojom::VideoDecoderPtr remote_decoder_;
   std::unique_ptr<MojoDecoderBufferWriter> mojo_decoder_buffer_writer_;
+
+  uint32_t writer_capacity_ = 0;
+
   bool remote_decoder_bound_ = false;
   bool has_connection_error_ = false;
   mojo::AssociatedBinding<mojom::VideoDecoderClient> client_binding_;
diff --git a/media/mojo/common/mojo_decoder_buffer_converter.cc b/media/mojo/common/mojo_decoder_buffer_converter.cc
index f361159d..df8dbf0 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter.cc
+++ b/media/mojo/common/mojo_decoder_buffer_converter.cc
@@ -19,7 +19,13 @@
 
 namespace {
 
-std::unique_ptr<mojo::DataPipe> CreateDataPipe(DemuxerStream::Type type) {
+bool IsPipeReadWriteError(MojoResult result) {
+  return result != MOJO_RESULT_OK && result != MOJO_RESULT_SHOULD_WAIT;
+}
+
+}  // namespace
+
+uint32_t GetDefaultDecoderBufferConverterCapacity(DemuxerStream::Type type) {
   uint32_t capacity = 0;
 
   if (type == DemuxerStream::AUDIO) {
@@ -36,26 +42,22 @@
     capacity = 512 * 1024;
   }
 
-  return base::MakeUnique<mojo::DataPipe>(capacity);
+  return capacity;
 }
 
-bool IsPipeReadWriteError(MojoResult result) {
-  return result != MOJO_RESULT_OK && result != MOJO_RESULT_SHOULD_WAIT;
-}
-
-}  // namespace
-
 // MojoDecoderBufferReader
 
 // static
 std::unique_ptr<MojoDecoderBufferReader> MojoDecoderBufferReader::Create(
-    DemuxerStream::Type type,
+    uint32_t capacity,
     mojo::ScopedDataPipeProducerHandle* producer_handle) {
   DVLOG(1) << __func__;
-  std::unique_ptr<mojo::DataPipe> data_pipe = CreateDataPipe(type);
+  DCHECK_GT(capacity, 0u);
+
+  auto data_pipe = std::make_unique<mojo::DataPipe>(capacity);
   *producer_handle = std::move(data_pipe->producer_handle);
-  return base::WrapUnique(
-      new MojoDecoderBufferReader(std::move(data_pipe->consumer_handle)));
+  return std::make_unique<MojoDecoderBufferReader>(
+      std::move(data_pipe->consumer_handle));
 }
 
 MojoDecoderBufferReader::MojoDecoderBufferReader(
@@ -81,6 +83,8 @@
 MojoDecoderBufferReader::~MojoDecoderBufferReader() {
   DVLOG(1) << __func__;
   CancelAllPendingReadCBs();
+  if (flush_cb_)
+    std::move(flush_cb_).Run();
 }
 
 void MojoDecoderBufferReader::CancelReadCB(ReadCB read_cb) {
@@ -101,6 +105,8 @@
 
 void MojoDecoderBufferReader::CompleteCurrentRead() {
   DVLOG(4) << __func__;
+  DCHECK(!pending_read_cbs_.empty());
+  DCHECK_EQ(pending_read_cbs_.size(), pending_buffers_.size());
 
   ReadCB read_cb = std::move(pending_read_cbs_.front());
   pending_read_cbs_.pop_front();
@@ -112,6 +118,9 @@
   bytes_read_ = 0;
 
   std::move(read_cb).Run(std::move(buffer));
+
+  if (pending_read_cbs_.empty() && flush_cb_)
+    std::move(flush_cb_).Run();
 }
 
 void MojoDecoderBufferReader::ScheduleNextRead() {
@@ -128,6 +137,7 @@
     mojom::DecoderBufferPtr mojo_buffer,
     ReadCB read_cb) {
   DVLOG(3) << __func__;
+  DCHECK(!flush_cb_);
 
   if (!consumer_handle_.is_valid()) {
     DCHECK(pending_read_cbs_.empty());
@@ -152,6 +162,22 @@
   ProcessPendingReads();
 }
 
+void MojoDecoderBufferReader::Flush(base::OnceClosure flush_cb) {
+  DVLOG(2) << __func__;
+  DCHECK(!flush_cb_);
+
+  if (pending_read_cbs_.empty()) {
+    std::move(flush_cb).Run();
+    return;
+  }
+
+  flush_cb_ = std::move(flush_cb);
+}
+
+bool MojoDecoderBufferReader::HasPendingReads() const {
+  return !pending_read_cbs_.empty();
+}
+
 void MojoDecoderBufferReader::OnPipeReadable(
     MojoResult result,
     const mojo::HandleSignalsState& state) {
@@ -213,6 +239,7 @@
     }
 
     DCHECK_EQ(result, MOJO_RESULT_OK);
+    DVLOG(4) << __func__ << ": " << num_bytes << " bytes read.";
     DCHECK_GT(num_bytes, 0u);
     bytes_read_ += num_bytes;
 
@@ -245,13 +272,15 @@
 
 // static
 std::unique_ptr<MojoDecoderBufferWriter> MojoDecoderBufferWriter::Create(
-    DemuxerStream::Type type,
+    uint32_t capacity,
     mojo::ScopedDataPipeConsumerHandle* consumer_handle) {
   DVLOG(1) << __func__;
-  std::unique_ptr<mojo::DataPipe> data_pipe = CreateDataPipe(type);
+  DCHECK_GT(capacity, 0u);
+
+  auto data_pipe = std::make_unique<mojo::DataPipe>(capacity);
   *consumer_handle = std::move(data_pipe->consumer_handle);
-  return base::WrapUnique(
-      new MojoDecoderBufferWriter(std::move(data_pipe->producer_handle)));
+  return std::make_unique<MojoDecoderBufferWriter>(
+      std::move(data_pipe->producer_handle));
 }
 
 MojoDecoderBufferWriter::MojoDecoderBufferWriter(
@@ -367,6 +396,7 @@
     }
 
     DCHECK_EQ(MOJO_RESULT_OK, result);
+    DVLOG(4) << __func__ << ": " << num_bytes << " bytes written.";
     DCHECK_GT(num_bytes, 0u);
     bytes_written_ += num_bytes;
     if (bytes_written_ == buffer_size) {
diff --git a/media/mojo/common/mojo_decoder_buffer_converter.h b/media/mojo/common/mojo_decoder_buffer_converter.h
index d3afd2b..538ff43 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter.h
+++ b/media/mojo/common/mojo_decoder_buffer_converter.h
@@ -19,15 +19,20 @@
 
 class DecoderBuffer;
 
+// Returns the default capacity to be used with MojoDecoderBufferReader and
+// MojoDecoderBufferWriter for |type|.
+uint32_t GetDefaultDecoderBufferConverterCapacity(DemuxerStream::Type type);
+
 // Combines mojom::DecoderBuffers with data read from a DataPipe to produce
 // media::DecoderBuffers (counterpart of MojoDecoderBufferWriter).
 class MojoDecoderBufferReader {
  public:
   using ReadCB = base::OnceCallback<void(scoped_refptr<DecoderBuffer>)>;
 
-  // Creates a MojoDecoderBufferReader of |type| and set the |producer_handle|.
+  // Creates a MojoDecoderBufferReader of |capacity| bytes and set the
+  // |producer_handle|.
   static std::unique_ptr<MojoDecoderBufferReader> Create(
-      DemuxerStream::Type type,
+      uint32_t capacity,
       mojo::ScopedDataPipeProducerHandle* producer_handle);
 
   // Hold the consumer handle to read DecoderBuffer data.
@@ -49,6 +54,16 @@
   // be called with nullptr.
   void ReadDecoderBuffer(mojom::DecoderBufferPtr buffer, ReadCB read_cb);
 
+  // Reads all pending data from the pipe and fire all pending ReadCBs, after
+  // which fire the |flush_cb|. No further ReadDecoderBuffer() or Flush() calls
+  // should be made before |flush_cb| is fired.
+  // Note that |flush_cb| may be called on the same call stack as this Flush()
+  // call if there are no pending reads.
+  void Flush(base::OnceClosure flush_cb);
+
+  // Whether there's any pending reads in |this|.
+  bool HasPendingReads() const;
+
  private:
   void CancelReadCB(ReadCB read_cb);
   void CancelAllPendingReadCBs();
@@ -71,6 +86,9 @@
   // Callbacks for pending buffers.
   base::circular_deque<ReadCB> pending_read_cbs_;
 
+  // Callback for Flush().
+  base::OnceClosure flush_cb_;
+
   // Number of bytes already read into the current buffer.
   uint32_t bytes_read_;
 
@@ -89,9 +107,10 @@
 // successful prior to the closure.
 class MojoDecoderBufferWriter {
  public:
-  // Creates a MojoDecoderBufferWriter of |type| and set the |consumer_handle|.
+  // Creates a MojoDecoderBufferWriter of |capacity| bytes and set the
+  // |consumer_handle|.
   static std::unique_ptr<MojoDecoderBufferWriter> Create(
-      DemuxerStream::Type type,
+      uint32_t capacity,
       mojo::ScopedDataPipeConsumerHandle* consumer_handle);
 
   // Hold the producer handle to write DecoderBuffer data.
diff --git a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
index a0fd834..e3b62a2 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
+++ b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
@@ -229,4 +229,169 @@
   run_loop.Run();
 }
 
+TEST(MojoDecoderBufferConverterTest, FlushWithoutRead) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+  EXPECT_CALL(mock_flush_cb, Run());
+
+  MojoDecoderBufferConverter converter;
+  converter.reader->Flush(mock_flush_cb.Get());
+
+  run_loop.RunUntilIdle();
+}
+
+TEST(MojoDecoderBufferConverterTest, FlushAfterRead) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
+  const size_t kDataSize = arraysize(kData);
+  scoped_refptr<DecoderBuffer> media_buffer =
+      DecoderBuffer::CopyFrom(kData, kDataSize);
+
+  MojoDecoderBufferConverter converter(kDataSize / 3);
+  converter.ConvertAndVerify(media_buffer);
+
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+  EXPECT_CALL(mock_flush_cb, Run())
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  converter.reader->Flush(mock_flush_cb.Get());
+
+  run_loop.Run();
+}
+
+TEST(MojoDecoderBufferConverterTest, FlushBeforeRead) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
+  const size_t kDataSize = arraysize(kData);
+  scoped_refptr<DecoderBuffer> media_buffer =
+      DecoderBuffer::CopyFrom(kData, kDataSize);
+
+  MojoDecoderBufferConverter converter;
+
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+
+  ::testing::InSequence sequence;
+  EXPECT_CALL(mock_flush_cb, Run());
+  EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)))
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  // Write, Flush, then Read.
+  mojom::DecoderBufferPtr mojo_buffer =
+      converter.writer->WriteDecoderBuffer(media_buffer);
+  converter.reader->Flush(mock_flush_cb.Get());
+  converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
+                                      mock_read_cb.Get());
+  run_loop.Run();
+}
+
+TEST(MojoDecoderBufferConverterTest, FlushBeforeChunkedRead) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
+  const size_t kDataSize = arraysize(kData);
+  scoped_refptr<DecoderBuffer> media_buffer =
+      DecoderBuffer::CopyFrom(kData, kDataSize);
+
+  MojoDecoderBufferConverter converter(kDataSize / 3);
+
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+
+  // Read callback should be fired after reset callback.
+  ::testing::InSequence sequence;
+  EXPECT_CALL(mock_flush_cb, Run());
+  EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)))
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  // Write, reset, then read.
+  mojom::DecoderBufferPtr mojo_buffer =
+      converter.writer->WriteDecoderBuffer(media_buffer);
+  converter.reader->Flush(mock_flush_cb.Get());
+  converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
+                                      mock_read_cb.Get());
+  run_loop.Run();
+}
+
+TEST(MojoDecoderBufferConverterTest, FlushDuringChunkedRead) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
+  const size_t kDataSize = arraysize(kData);
+  scoped_refptr<DecoderBuffer> media_buffer =
+      DecoderBuffer::CopyFrom(kData, kDataSize);
+
+  MojoDecoderBufferConverter converter(kDataSize / 3);
+
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+
+  // Flush callback should be fired after read callback.
+  ::testing::InSequence sequence;
+  EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)));
+  EXPECT_CALL(mock_flush_cb, Run())
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  // Write, read, then reset.
+  mojom::DecoderBufferPtr mojo_buffer =
+      converter.writer->WriteDecoderBuffer(media_buffer);
+  converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
+                                      mock_read_cb.Get());
+  converter.reader->Flush(mock_flush_cb.Get());
+  run_loop.Run();
+}
+
+TEST(MojoDecoderBufferConverterTest, FlushDuringConcurrentReads) {
+  base::MessageLoop message_loop;
+  base::RunLoop run_loop;
+
+  // Prevent all of the buffers from fitting at once to exercise the chunking
+  // logic.
+  MojoDecoderBufferConverter converter(4);
+  auto& writer = converter.writer;
+  auto& reader = converter.reader;
+
+  // Three buffers: normal, EOS, normal.
+  const uint8_t kData[] = "Hello, world";
+  const size_t kDataSize = arraysize(kData);
+  auto media_buffer1 = DecoderBuffer::CopyFrom(kData, kDataSize);
+  auto media_buffer2 = DecoderBuffer::CreateEOSBuffer();
+  auto media_buffer3 = DecoderBuffer::CopyFrom(kData, kDataSize);
+
+  // Expect the read callbacks to be issued in the same order.
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb1;
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb2;
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb3;
+  base::MockCallback<base::OnceClosure> mock_flush_cb;
+
+  ::testing::InSequence scoper;
+  EXPECT_CALL(mock_read_cb1, Run(MatchesDecoderBuffer(media_buffer1)));
+  EXPECT_CALL(mock_read_cb2, Run(MatchesDecoderBuffer(media_buffer2)));
+  EXPECT_CALL(mock_read_cb3, Run(MatchesDecoderBuffer(media_buffer3)));
+  EXPECT_CALL(mock_flush_cb, Run())
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  // Write all of the buffers at once.
+  auto mojo_buffer1 = writer->WriteDecoderBuffer(media_buffer1);
+  auto mojo_buffer2 = writer->WriteDecoderBuffer(media_buffer2);
+  auto mojo_buffer3 = writer->WriteDecoderBuffer(media_buffer3);
+
+  // Read all of the buffers at once.
+  reader->ReadDecoderBuffer(std::move(mojo_buffer1), mock_read_cb1.Get());
+  reader->ReadDecoderBuffer(std::move(mojo_buffer2), mock_read_cb2.Get());
+  reader->ReadDecoderBuffer(std::move(mojo_buffer3), mock_read_cb3.Get());
+  reader->Flush(mock_flush_cb.Get());
+  // No ReadDecoderBuffer() can be called during pending reset.
+
+  run_loop.Run();
+}
+
 }  // namespace media
diff --git a/media/mojo/interfaces/decryptor.mojom b/media/mojo/interfaces/decryptor.mojom
index 19ec61a9..041ca55d 100644
--- a/media/mojo/interfaces/decryptor.mojom
+++ b/media/mojo/interfaces/decryptor.mojom
@@ -8,6 +8,8 @@
 
 // Interface for decrypting (and decoding) encrypted streams.
 // See media/base/decryptor.h for details.
+// TODO(crbug.com/794326): Deduplicate this interface with audio_decoder.mojom
+// and audio_decoder.mojom.
 interface Decryptor {
   // Status of a decrypt or decrypt-and-decode operation. See decryptor.h for
   // descriptions.
@@ -18,14 +20,15 @@
   [Native]
   enum StreamType;
 
-  // Pass the two data pipes used to transfer DecoderBuffer contents to and
-  // from the Decryptor. |receive_pipe| will be used to receive DecoderBuffer
-  // data on Decrypt(), DecryptAndDecodeAudio(), and DecryptAndDecodeVideo()
-  // calls. |transmit_pipe| will be used to pass the DecoderBuffer data
-  // back with OnDecryptDone() calls. This method must be called before any
-  // methods listed are called.
-  Initialize(handle<data_pipe_consumer> receive_pipe,
-             handle<data_pipe_producer> transmit_pipe);
+  // Pass three data pipes used to transfer compressed DecoderBuffer data for
+  // DecryptAndDecodeAudio(), and DecryptAndDecodeVideo() and Decrypt(),
+  // respectively, and one data pipe to receive DecoderBuffer data for decrypt
+  // result passed back in OnDecryptDone() calls.
+  // This method must be called before any methods listed are called.
+  Initialize(handle<data_pipe_consumer> audio_pipe,
+             handle<data_pipe_consumer> video_pipe,
+             handle<data_pipe_consumer> decrypt_pipe,
+             handle<data_pipe_producer> decrypted_pipe);
 
   // Decrypts the |encrypted| buffer and returns the decrypt |status| and
   // decrypted |buffer|.
diff --git a/media/mojo/services/mojo_audio_decoder_service.cc b/media/mojo/services/mojo_audio_decoder_service.cc
index b563711..486ff4e 100644
--- a/media/mojo/services/mojo_audio_decoder_service.cc
+++ b/media/mojo/services/mojo_audio_decoder_service.cc
@@ -82,8 +82,11 @@
 
 void MojoAudioDecoderService::Reset(ResetCallback callback) {
   DVLOG(1) << __func__;
-  decoder_->Reset(base::Bind(&MojoAudioDecoderService::OnResetDone, weak_this_,
-                             base::Passed(&callback)));
+
+  // Reset the reader so that pending decodes will be dispatches first.
+  mojo_decoder_buffer_reader_->Flush(
+      base::Bind(&MojoAudioDecoderService::OnReaderFlushDone, weak_this_,
+                 base::Passed(&callback)));
 }
 
 void MojoAudioDecoderService::OnInitialized(
@@ -118,6 +121,11 @@
                                       weak_this_, base::Passed(&callback)));
 }
 
+void MojoAudioDecoderService::OnReaderFlushDone(ResetCallback callback) {
+  decoder_->Reset(base::Bind(&MojoAudioDecoderService::OnResetDone, weak_this_,
+                             base::Passed(&callback)));
+}
+
 void MojoAudioDecoderService::OnDecodeStatus(DecodeCallback callback,
                                              media::DecodeStatus status) {
   DVLOG(3) << __func__ << " status:" << status;
diff --git a/media/mojo/services/mojo_audio_decoder_service.h b/media/mojo/services/mojo_audio_decoder_service.h
index 8b0ba5df..8530073 100644
--- a/media/mojo/services/mojo_audio_decoder_service.h
+++ b/media/mojo/services/mojo_audio_decoder_service.h
@@ -46,8 +46,12 @@
                      scoped_refptr<ContentDecryptionModule> cdm,
                      bool success);
 
+  // Called by |mojo_decoder_buffer_reader_| when read is finished.
   void OnReadDone(DecodeCallback callback, scoped_refptr<DecoderBuffer> buffer);
 
+  // Called by |mojo_decoder_buffer_reader_| when reset is finished.
+  void OnReaderFlushDone(ResetCallback callback);
+
   // Called by |decoder_| when DecoderBuffer is accepted or rejected.
   void OnDecodeStatus(DecodeCallback callback, media::DecodeStatus status);
 
diff --git a/media/mojo/services/mojo_decryptor_service.cc b/media/mojo/services/mojo_decryptor_service.cc
index 7d3c8d4..55a5704 100644
--- a/media/mojo/services/mojo_decryptor_service.cc
+++ b/media/mojo/services/mojo_decryptor_service.cc
@@ -61,19 +61,25 @@
 MojoDecryptorService::~MojoDecryptorService() = default;
 
 void MojoDecryptorService::Initialize(
-    mojo::ScopedDataPipeConsumerHandle receive_pipe,
-    mojo::ScopedDataPipeProducerHandle transmit_pipe) {
-  mojo_decoder_buffer_writer_.reset(
-      new MojoDecoderBufferWriter(std::move(transmit_pipe)));
-  mojo_decoder_buffer_reader_.reset(
-      new MojoDecoderBufferReader(std::move(receive_pipe)));
+    mojo::ScopedDataPipeConsumerHandle audio_pipe,
+    mojo::ScopedDataPipeConsumerHandle video_pipe,
+    mojo::ScopedDataPipeConsumerHandle decrypt_pipe,
+    mojo::ScopedDataPipeProducerHandle decrypted_pipe) {
+  audio_buffer_reader_.reset(
+      new MojoDecoderBufferReader(std::move(audio_pipe)));
+  video_buffer_reader_.reset(
+      new MojoDecoderBufferReader(std::move(video_pipe)));
+  decrypt_buffer_reader_.reset(
+      new MojoDecoderBufferReader(std::move(decrypt_pipe)));
+  decrypted_buffer_writer_.reset(
+      new MojoDecoderBufferWriter(std::move(decrypted_pipe)));
 }
 
 void MojoDecryptorService::Decrypt(StreamType stream_type,
                                    mojom::DecoderBufferPtr encrypted,
                                    DecryptCallback callback) {
   DVLOG(3) << __func__;
-  mojo_decoder_buffer_reader_->ReadDecoderBuffer(
+  decrypt_buffer_reader_->ReadDecoderBuffer(
       std::move(encrypted),
       base::BindOnce(&MojoDecryptorService::OnReadDone, weak_this_, stream_type,
                      std::move(callback)));
@@ -106,7 +112,7 @@
     mojom::DecoderBufferPtr encrypted,
     DecryptAndDecodeAudioCallback callback) {
   DVLOG(3) << __func__;
-  mojo_decoder_buffer_reader_->ReadDecoderBuffer(
+  audio_buffer_reader_->ReadDecoderBuffer(
       std::move(encrypted), base::BindOnce(&MojoDecryptorService::OnAudioRead,
                                            weak_this_, std::move(callback)));
 }
@@ -115,18 +121,28 @@
     mojom::DecoderBufferPtr encrypted,
     DecryptAndDecodeVideoCallback callback) {
   DVLOG(3) << __func__;
-  mojo_decoder_buffer_reader_->ReadDecoderBuffer(
+  video_buffer_reader_->ReadDecoderBuffer(
       std::move(encrypted), base::BindOnce(&MojoDecryptorService::OnVideoRead,
                                            weak_this_, std::move(callback)));
 }
 
 void MojoDecryptorService::ResetDecoder(StreamType stream_type) {
-  DVLOG(1) << __func__;
-  decryptor_->ResetDecoder(stream_type);
+  DVLOG(1) << __func__ << ": stream_type = " << stream_type;
+
+  // Reset the reader so that pending decodes will be dispatched first.
+  if (!GetBufferReader(stream_type))
+    return;
+
+  GetBufferReader(stream_type)
+      ->Flush(base::Bind(&MojoDecryptorService::OnReaderFlushDone, weak_this_,
+                         stream_type));
 }
 
 void MojoDecryptorService::DeinitializeDecoder(StreamType stream_type) {
   DVLOG(1) << __func__;
+  DCHECK(!GetBufferReader(stream_type)->HasPendingReads())
+      << "The decoder should be fully flushed before deinitialized.";
+
   decryptor_->DeinitializeDecoder(stream_type);
 }
 
@@ -157,7 +173,7 @@
   }
 
   mojom::DecoderBufferPtr mojo_buffer =
-      mojo_decoder_buffer_writer_->WriteDecoderBuffer(buffer);
+      decrypted_buffer_writer_->WriteDecoderBuffer(buffer);
   if (!mojo_buffer) {
     std::move(callback).Run(Status::kError, nullptr);
     return;
@@ -205,6 +221,11 @@
                                     weak_this_, base::Passed(&callback)));
 }
 
+void MojoDecryptorService::OnReaderFlushDone(StreamType stream_type) {
+  DVLOG(1) << __func__ << ": stream_type = " << stream_type;
+  decryptor_->ResetDecoder(stream_type);
+}
+
 void MojoDecryptorService::OnAudioDecoded(
     DecryptAndDecodeAudioCallback callback,
     Status status,
@@ -246,4 +267,17 @@
   std::move(callback).Run(status, std::move(frame), std::move(releaser));
 }
 
+MojoDecoderBufferReader* MojoDecryptorService::GetBufferReader(
+    StreamType stream_type) const {
+  switch (stream_type) {
+    case StreamType::kAudio:
+      return audio_buffer_reader_.get();
+    case StreamType::kVideo:
+      return video_buffer_reader_.get();
+  }
+
+  NOTREACHED() << "Unexpected stream_type: " << stream_type;
+  return nullptr;
+}
+
 }  // namespace media
diff --git a/media/mojo/services/mojo_decryptor_service.h b/media/mojo/services/mojo_decryptor_service.h
index 691f03e..232b211 100644
--- a/media/mojo/services/mojo_decryptor_service.h
+++ b/media/mojo/services/mojo_decryptor_service.h
@@ -41,8 +41,10 @@
   ~MojoDecryptorService() final;
 
   // mojom::Decryptor implementation.
-  void Initialize(mojo::ScopedDataPipeConsumerHandle receive_pipe,
-                  mojo::ScopedDataPipeProducerHandle transmit_pipe) final;
+  void Initialize(mojo::ScopedDataPipeConsumerHandle audio_pipe,
+                  mojo::ScopedDataPipeConsumerHandle video_pipe,
+                  mojo::ScopedDataPipeConsumerHandle decrypt_pipe,
+                  mojo::ScopedDataPipeProducerHandle decrypted_pipe) final;
   void Decrypt(StreamType stream_type,
                mojom::DecoderBufferPtr encrypted,
                DecryptCallback callback) final;
@@ -78,6 +80,7 @@
                    scoped_refptr<DecoderBuffer> buffer);
   void OnVideoRead(DecryptAndDecodeVideoCallback callback,
                    scoped_refptr<DecoderBuffer> buffer);
+  void OnReaderFlushDone(StreamType stream_type);
 
   // Callbacks executed when DecryptAndDecode are done.
   void OnAudioDecoded(DecryptAndDecodeAudioCallback callback,
@@ -87,14 +90,19 @@
                       Status status,
                       const scoped_refptr<VideoFrame>& frame);
 
+  // Returns audio/video buffer reader according to the |stream_type|.
+  MojoDecoderBufferReader* GetBufferReader(StreamType stream_type) const;
+
   // A weak binding is used to connect to the MojoDecryptor.
   mojo::Binding<mojom::Decryptor> binding_;
 
-  // Helper class to send decrypted DecoderBuffer to the client.
-  std::unique_ptr<MojoDecoderBufferWriter> mojo_decoder_buffer_writer_;
+  // Helper classes to receive encrypted DecoderBuffer from the client.
+  std::unique_ptr<MojoDecoderBufferReader> audio_buffer_reader_;
+  std::unique_ptr<MojoDecoderBufferReader> video_buffer_reader_;
+  std::unique_ptr<MojoDecoderBufferReader> decrypt_buffer_reader_;
 
-  // Helper class to receive encrypted DecoderBuffer from the client.
-  std::unique_ptr<MojoDecoderBufferReader> mojo_decoder_buffer_reader_;
+  // Helper class to send decrypted DecoderBuffer to the client.
+  std::unique_ptr<MojoDecoderBufferWriter> decrypted_buffer_writer_;
 
   media::Decryptor* decryptor_;
 
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 5cf0b8f..1d1a2ff 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -192,7 +192,7 @@
   }
 
   mojo_decoder_buffer_reader_->ReadDecoderBuffer(
-      std::move(buffer), base::BindOnce(&MojoVideoDecoderService::OnDecoderRead,
+      std::move(buffer), base::BindOnce(&MojoVideoDecoderService::OnReaderRead,
                                         weak_this_, std::move(callback)));
 }
 
@@ -204,8 +204,10 @@
     return;
   }
 
-  decoder_->Reset(base::Bind(&MojoVideoDecoderService::OnDecoderReset,
-                             weak_this_, base::Passed(&callback)));
+  // Flush the reader so that pending decodes will be dispatches first.
+  mojo_decoder_buffer_reader_->Flush(
+      base::Bind(&MojoVideoDecoderService::OnReaderFlushed, weak_this_,
+                 base::Passed(&callback)));
 }
 
 void MojoVideoDecoderService::OnDecoderInitialized(
@@ -222,7 +224,7 @@
                           decoder_->GetMaxDecodeRequests());
 }
 
-void MojoVideoDecoderService::OnDecoderRead(
+void MojoVideoDecoderService::OnReaderRead(
     DecodeCallback callback,
     scoped_refptr<DecoderBuffer> buffer) {
   DVLOG(3) << __func__;
@@ -237,6 +239,11 @@
                          base::Passed(&callback)));
 }
 
+void MojoVideoDecoderService::OnReaderFlushed(ResetCallback callback) {
+  decoder_->Reset(base::Bind(&MojoVideoDecoderService::OnDecoderReset,
+                             weak_this_, base::Passed(&callback)));
+}
+
 void MojoVideoDecoderService::OnDecoderDecoded(DecodeCallback callback,
                                                DecodeStatus status) {
   DVLOG(2) << __func__;
diff --git a/media/mojo/services/mojo_video_decoder_service.h b/media/mojo/services/mojo_video_decoder_service.h
index e2692745..06215c4b 100644
--- a/media/mojo/services/mojo_video_decoder_service.h
+++ b/media/mojo/services/mojo_video_decoder_service.h
@@ -63,9 +63,13 @@
   void OnDecoderInitialized(InitializeCallback callback,
                             scoped_refptr<ContentDecryptionModule> cdm,
                             bool success);
-  void OnDecoderRead(DecodeCallback callback,
-                     scoped_refptr<DecoderBuffer> buffer);
+  void OnReaderRead(DecodeCallback callback,
+                    scoped_refptr<DecoderBuffer> buffer);
   void OnDecoderDecoded(DecodeCallback callback, DecodeStatus status);
+
+  // Called by |mojo_decoder_buffer_reader_| when reset is finished.
+  void OnReaderFlushed(ResetCallback callback);
+
   void OnDecoderReset(ResetCallback callback);
   void OnDecoderOutput(const scoped_refptr<VideoFrame>& frame);
 
diff --git a/media/mojo/test/mojo_video_decoder_integration_test.cc b/media/mojo/test/mojo_video_decoder_integration_test.cc
index 51ccf533..2f0ae631 100644
--- a/media/mojo/test/mojo_video_decoder_integration_test.cc
+++ b/media/mojo/test/mojo_video_decoder_integration_test.cc
@@ -184,6 +184,10 @@
  protected:
   void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
 
+  void SetWriterCapacity(uint32_t capacity) {
+    client_->set_writer_capacity_for_testing(capacity);
+  }
+
   bool Initialize() {
     bool result = false;
 
@@ -220,12 +224,16 @@
   }
 
   scoped_refptr<DecoderBuffer> CreateKeyframe(int64_t timestamp_ms) {
-    std::vector<uint8_t> data = {1, 2, 3, 4};
+    // Use 32 bytes to simulated chunked write (with capacity 10; see below).
+    std::vector<uint8_t> data(32, 0);
+
     scoped_refptr<DecoderBuffer> buffer =
         DecoderBuffer::CopyFrom(data.data(), data.size());
+
     buffer->set_timestamp(base::TimeDelta::FromMilliseconds(timestamp_ms));
     buffer->set_duration(base::TimeDelta::FromMilliseconds(10));
     buffer->set_is_key_frame(true);
+
     return buffer;
   }
 
@@ -349,4 +357,34 @@
   RunUntilIdle();
 }
 
+TEST_F(MojoVideoDecoderIntegrationTest, ResetDuringDecode_ChunkedWrite) {
+  // Use a small writer capacity to force chunked write.
+  SetWriterCapacity(10);
+
+  ASSERT_TRUE(Initialize());
+
+  VideoFrame::ReleaseMailboxCB release_cb = VideoFrame::ReleaseMailboxCB();
+  StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
+  StrictMock<base::MockCallback<base::Closure>> reset_cb;
+
+  EXPECT_CALL(*decoder_, GetReleaseMailboxCB())
+      .WillRepeatedly(Return(release_cb));
+  EXPECT_CALL(output_cb_, Run(_)).Times(kMaxDecodeRequests);
+  EXPECT_CALL(*decoder_, Decode(_, _)).Times(kMaxDecodeRequests);
+  EXPECT_CALL(*decoder_, Reset(_)).Times(1);
+
+  InSequence s;  // Make sure all callbacks are fired in order.
+  EXPECT_CALL(decode_cb, Run(_)).Times(kMaxDecodeRequests);
+  EXPECT_CALL(reset_cb, Run());
+
+  int64_t timestamp_ms = 0;
+  for (int j = 0; j < kMaxDecodeRequests; ++j) {
+    client_->Decode(CreateKeyframe(timestamp_ms++), decode_cb.Get());
+  }
+
+  client_->Reset(reset_cb.Get());
+
+  RunUntilIdle();
+}
+
 }  // namespace media
diff --git a/services/shape_detection/BUILD.gn b/services/shape_detection/BUILD.gn
index 1f47289..36d2ff0 100644
--- a/services/shape_detection/BUILD.gn
+++ b/services/shape_detection/BUILD.gn
@@ -37,7 +37,8 @@
       "face_detection_impl_win.h",
       "face_detection_provider_win.cc",
       "face_detection_provider_win.h",
-      "text_detection_impl.cc",
+      "text_detection_impl_win.cc",
+      "text_detection_impl_win.h",
     ]
   } else {
     sources += [
@@ -109,21 +110,19 @@
 
 source_set("tests") {
   testonly = true
-  sources = []
+  sources = [
+    "barcode_detection_impl_mac_unittest.mm",
+    "face_detection_impl_mac_unittest.mm",
+    "face_detection_impl_win_unittest.cc",
+    "text_detection_impl_mac_unittest.mm",
+    "text_detection_impl_win_unittest.cc",
+  ]
   if (is_mac) {
-    sources += [
-      "barcode_detection_impl_mac_unittest.mm",
-      "face_detection_impl_mac_unittest.mm",
-      "text_detection_impl_mac_unittest.mm",
-    ]
-
     libs = [
       "CoreFoundation.framework",
       "CoreGraphics.framework",
       "QuartzCore.framework",
     ]
-  } else if (is_win) {
-    sources += [ "face_detection_impl_win_unittest.cc" ]
   }
 
   deps = [
diff --git a/services/shape_detection/text_detection_impl.h b/services/shape_detection/text_detection_impl.h
index 617f6e8..30e8400a1 100644
--- a/services/shape_detection/text_detection_impl.h
+++ b/services/shape_detection/text_detection_impl.h
@@ -11,6 +11,7 @@
 
 class TextDetectionImpl {
  public:
+  // Binds TextDetection request to an implementation of mojom::TextDetection.
   static void Create(mojom::TextDetectionRequest request);
 
  private:
diff --git a/services/shape_detection/text_detection_impl_win.cc b/services/shape_detection/text_detection_impl_win.cc
new file mode 100644
index 0000000..b1522e9
--- /dev/null
+++ b/services/shape_detection/text_detection_impl_win.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/shape_detection/text_detection_impl_win.h"
+
+#include <windows.globalization.h>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/win/core_winrt_util.h"
+#include "base/win/scoped_hstring.h"
+#include "base/win/windows_version.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/shape_detection/text_detection_impl.h"
+
+namespace shape_detection {
+
+using ABI::Windows::Globalization::ILanguageFactory;
+using ABI::Windows::Media::Ocr::IOcrEngineStatics;
+using base::win::GetActivationFactory;
+using base::win::ScopedHString;
+
+// static
+void TextDetectionImpl::Create(mojom::TextDetectionRequest request) {
+  // OcrEngine class is only available in Win 10 onwards (v10.0.10240.0) that
+  // documents in
+  // https://docs.microsoft.com/en-us/uwp/api/windows.media.ocr.ocrengine.
+  if (base::win::GetVersion() < base::win::VERSION_WIN10) {
+    DVLOG(1) << "Optical character recognition not supported before Windows 10";
+    return;
+  }
+  DCHECK_GE(base::win::OSInfo::GetInstance()->version_number().build, 10240);
+
+  // Loads functions dynamically at runtime to prevent library dependencies.
+  if (!(base::win::ResolveCoreWinRTDelayload() &&
+        ScopedHString::ResolveCoreWinRTStringDelayload())) {
+    DLOG(ERROR) << "Failed loading functions from combase.dll";
+    return;
+  }
+
+  // Text Detection specification only supports Latin-1 text as documented in
+  // https://wicg.github.io/shape-detection-api/text.html#text-detection-api.
+  // TODO(junwei.fu): https://crbug.com/794097 consider supporting other Latin
+  // script language.
+  ScopedHString language_hstring = ScopedHString::Create("en");
+  if (!language_hstring.is_valid())
+    return;
+
+  Microsoft::WRL::ComPtr<ILanguageFactory> language_factory;
+  HRESULT hr =
+      GetActivationFactory<ILanguageFactory,
+                           RuntimeClass_Windows_Globalization_Language>(
+          &language_factory);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "ILanguage factory failed: "
+                << logging::SystemErrorCodeToString(hr);
+    return;
+  }
+
+  Microsoft::WRL::ComPtr<ABI::Windows::Globalization::ILanguage> language;
+  hr = language_factory->CreateLanguage(language_hstring.get(), &language);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Create language failed: "
+                << logging::SystemErrorCodeToString(hr);
+    return;
+  }
+
+  Microsoft::WRL::ComPtr<IOcrEngineStatics> engine_factory;
+  hr = GetActivationFactory<IOcrEngineStatics,
+                            RuntimeClass_Windows_Media_Ocr_OcrEngine>(
+      &engine_factory);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "IOcrEngineStatics factory failed: "
+                << logging::SystemErrorCodeToString(hr);
+    return;
+  }
+
+  boolean is_supported = false;
+  hr = engine_factory->IsLanguageSupported(language.Get(), &is_supported);
+  if (FAILED(hr) || !is_supported)
+    return;
+
+  Microsoft::WRL::ComPtr<IOcrEngine> ocr_engine;
+  hr = engine_factory->TryCreateFromLanguage(language.Get(), &ocr_engine);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Create engine failed from language: "
+                << logging::SystemErrorCodeToString(hr);
+    return;
+  }
+
+  mojo::MakeStrongBinding(
+      std::make_unique<TextDetectionImplWin>(std::move(ocr_engine)),
+      std::move(request));
+}
+
+TextDetectionImplWin::TextDetectionImplWin(
+    Microsoft::WRL::ComPtr<IOcrEngine> ocr_engine)
+    : ocr_engine_(std::move(ocr_engine)) {
+  DCHECK(ocr_engine_);
+}
+
+TextDetectionImplWin::~TextDetectionImplWin() = default;
+
+void TextDetectionImplWin::Detect(const SkBitmap& bitmap,
+                                  DetectCallback callback) {
+  std::move(callback).Run(std::vector<mojom::TextDetectionResultPtr>());
+}
+
+}  // namespace shape_detection
diff --git a/services/shape_detection/text_detection_impl_win.h b/services/shape_detection/text_detection_impl_win.h
new file mode 100644
index 0000000..52e78d5a
--- /dev/null
+++ b/services/shape_detection/text_detection_impl_win.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_SHAPE_DETECTION_TEXT_DETECTION_IMPL_WIN_H_
+#define SERVICES_SHAPE_DETECTION_TEXT_DETECTION_IMPL_WIN_H_
+
+#include <windows.media.ocr.h>
+#include <wrl/client.h>
+
+#include "base/macros.h"
+#include "services/shape_detection/public/interfaces/textdetection.mojom.h"
+
+class SkBitmap;
+
+namespace shape_detection {
+
+using ABI::Windows::Media::Ocr::IOcrEngine;
+
+class TextDetectionImplWin : public mojom::TextDetection {
+ public:
+  explicit TextDetectionImplWin(Microsoft::WRL::ComPtr<IOcrEngine> ocr_engine);
+  ~TextDetectionImplWin() override;
+
+  // mojom::TextDetection implementation.
+  void Detect(const SkBitmap& bitmap,
+              mojom::TextDetection::DetectCallback callback) override;
+
+ private:
+  Microsoft::WRL::ComPtr<IOcrEngine> ocr_engine_;
+
+  DISALLOW_COPY_AND_ASSIGN(TextDetectionImplWin);
+};
+
+}  // namespace shape_detection
+
+#endif  // SERVICES_SHAPE_DETECTION_TEXT_DETECTION_IMPL_WIN_H_
diff --git a/services/shape_detection/text_detection_impl_win_unittest.cc b/services/shape_detection/text_detection_impl_win_unittest.cc
new file mode 100644
index 0000000..c8a286ef
--- /dev/null
+++ b/services/shape_detection/text_detection_impl_win_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/win/scoped_com_initializer.h"
+#include "base/win/windows_version.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/shape_detection/public/interfaces/textdetection.mojom.h"
+#include "services/shape_detection/text_detection_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkImage.h"
+
+namespace shape_detection {
+
+namespace {
+
+void DetectTextCallback(base::Closure quit_closure,
+                        size_t* num_text_chunks,
+                        std::vector<mojom::TextDetectionResultPtr> results) {
+  *num_text_chunks = results.size();
+  quit_closure.Run();
+}
+
+}  // namespace
+
+class TextDetectionImplWinTest : public testing::Test {
+ protected:
+  TextDetectionImplWinTest() = default;
+  ~TextDetectionImplWinTest() override = default;
+
+  void SetUp() override {
+    scoped_com_initializer_ = std::make_unique<base::win::ScopedCOMInitializer>(
+        base::win::ScopedCOMInitializer::kMTA);
+    ASSERT_TRUE(scoped_com_initializer_->Succeeded());
+  }
+
+ private:
+  std::unique_ptr<base::win::ScopedCOMInitializer> scoped_com_initializer_;
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  DISALLOW_COPY_AND_ASSIGN(TextDetectionImplWinTest);
+};
+
+TEST_F(TextDetectionImplWinTest, ScanOnce) {
+  // OCR not supported before Windows 10
+  if (base::win::GetVersion() < base::win::VERSION_WIN10)
+    return;
+
+  mojom::TextDetectionPtr text_service;
+  auto request = mojo::MakeRequest(&text_service);
+  TextDetectionImpl::Create(std::move(request));
+
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(100, 100);
+  bitmap.eraseColor(SK_ColorBLUE);
+
+  base::RunLoop run_loop;
+  size_t num_text_chunks = 1u;
+  text_service->Detect(
+      bitmap, base::BindOnce(&DetectTextCallback, run_loop.QuitClosure(),
+                             &num_text_chunks));
+  run_loop.Run();
+  EXPECT_EQ(0u, num_text_chunks);
+}
+
+}  // namespace shape_detection
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/selectors/invalidation/any-link-pseudo.html b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/invalidation/any-link-pseudo.html
new file mode 100644
index 0000000..9792fd0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/selectors/invalidation/any-link-pseudo.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors Invalidation: :any-link</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-any-link-pseudo">
+    <meta name="assert" content="This tests that the :any-link selector is effective">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      #link { background-color: red }
+      #link:any-link { background-color: green }
+      #link + div { color: pink }
+    </style>
+    <a id="link">This link should have a green background.</a>
+    <div>
+        <div></div>
+        <div></div>
+        <div></div>
+        <div></div>
+    </div>
+    <script>
+      test(function() {
+        var red = "rgb(255, 0, 0)";
+        var green = "rgb(0, 128, 0)";
+
+        assert_equals(getComputedStyle(link).backgroundColor, red);
+
+        link.href = "not-visited.html";
+
+        assert_equals(getComputedStyle(link).backgroundColor, green);
+      }, "Style was recalculated for the :any-link pseudo class.");
+
+    </script>
+  </head>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo-expected.txt b/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo-expected.txt
index 5ce6faf..79b403c 100644
--- a/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo-expected.txt
@@ -1,4 +1,4 @@
-Use descendant invalidation set for :-webkit-any-link pseudo class.
+Use descendant invalidation set for :any-link pseudo class.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
diff --git a/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo.html b/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo.html
index b9daa2e..5abc1668 100644
--- a/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo.html
+++ b/third_party/WebKit/LayoutTests/fast/css/invalidation/any-link-pseudo.html
@@ -2,7 +2,7 @@
 <script src="../../../resources/js-test.js"></script>
 <style>
 #link { background-color: red }
-#link:-webkit-any-link { background-color: green }
+#link:any-link { background-color: green }
 #link + div { color: pink }
 </style>
 <a id="link">This link should have a green background.</a>
@@ -13,7 +13,7 @@
     <div></div>
 </div>
 <script>
-description("Use descendant invalidation set for :-webkit-any-link pseudo class.")
+description("Use descendant invalidation set for :any-link pseudo class.")
 
 var red = "rgb(255, 0, 0)";
 var green = "rgb(0, 128, 0)";
diff --git a/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt b/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
new file mode 100644
index 0000000..5ce6faf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
@@ -0,0 +1,13 @@
+Use descendant invalidation set for :-webkit-any-link pseudo class.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS getComputedStyle(link).backgroundColor is red
+PASS internals.updateStyleAndReturnAffectedElementCount() is 1
+PASS getComputedStyle(link).backgroundColor is green
+PASS successfullyParsed is true
+
+TEST COMPLETE
+This link should have a green background.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo.html b/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo.html
new file mode 100644
index 0000000..b9daa2e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/invalidation/webkit-any-link-pseudo.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="../../../resources/js-test.js"></script>
+<style>
+#link { background-color: red }
+#link:-webkit-any-link { background-color: green }
+#link + div { color: pink }
+</style>
+<a id="link">This link should have a green background.</a>
+<div>
+    <div></div>
+    <div></div>
+    <div></div>
+    <div></div>
+</div>
+<script>
+description("Use descendant invalidation set for :-webkit-any-link pseudo class.")
+
+var red = "rgb(255, 0, 0)";
+var green = "rgb(0, 128, 0)";
+
+shouldBe("getComputedStyle(link).backgroundColor", "red");
+
+link.offsetTop; // Force recalc.
+link.href = "not-visited.html";
+
+if (window.internals)
+    shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "1");
+
+shouldBe("getComputedStyle(link).backgroundColor", "green");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/usecounter-pseudo-webkit-any-link.html b/third_party/WebKit/LayoutTests/fast/css/usecounter-pseudo-webkit-any-link.html
deleted file mode 100644
index dbd9a8e..0000000
--- a/third_party/WebKit/LayoutTests/fast/css/usecounter-pseudo-webkit-any-link.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Use counter test for :-webkit-any-link</title>
-    <script src="../../resources/testharness.js"></script>
-    <script src="../../resources/testharnessreport.js"></script>
-    <style id="style"></style>
-    <a href="http://google.com" id="link">Link</a>
-    <script>
-      'use strict';
-      test(() => {
-        let CSSSelectorPseudoWebkitAnyLink = 2260; // From UseCounter.h
-        let isCounted = () => internals.isUseCounted(document, CSSSelectorPseudoWebkitAnyLink);
-        var div = document.createElement('div');
-        getComputedStyle(link);
-        assert_false(isCounted(), ':-webkit-any-link should not be counted');
-        var style = document.getElementById('style');
-        style.textContent = ':-webkit-any-link { color: blue; }';
-        assert_equals(getComputedStyle(link).color, 'rgb(0, 0, 255)');
-        assert_true(isCounted(), ':-webkit-any-link should be counted');
-      }, ':-webkit-any-link counter is working');
-    </script>
-  </head>
-<html>
\ No newline at end of file
diff --git a/third_party/WebKit/Source/core/css/CSSSelector.cpp b/third_party/WebKit/Source/core/css/CSSSelector.cpp
index f170482..1d3af474 100644
--- a/third_party/WebKit/Source/core/css/CSSSelector.cpp
+++ b/third_party/WebKit/Source/core/css/CSSSelector.cpp
@@ -215,6 +215,7 @@
     case kPseudoVisited:
     case kPseudoAny:
     case kPseudoAnyLink:
+    case kPseudoWebkitAnyLink:
     case kPseudoAutofill:
     case kPseudoHover:
     case kPseudoDrag:
@@ -303,7 +304,7 @@
     {"-internal-video-persistent", CSSSelector::kPseudoVideoPersistent},
     {"-internal-video-persistent-ancestor",
      CSSSelector::kPseudoVideoPersistentAncestor},
-    {"-webkit-any-link", CSSSelector::kPseudoAnyLink},
+    {"-webkit-any-link", CSSSelector::kPseudoWebkitAnyLink},
     {"-webkit-autofill", CSSSelector::kPseudoAutofill},
     {"-webkit-drag", CSSSelector::kPseudoDrag},
     {"-webkit-full-page-media", CSSSelector::kPseudoFullPageMedia},
@@ -318,6 +319,7 @@
     {"-webkit-scrollbar-track-piece", CSSSelector::kPseudoScrollbarTrackPiece},
     {"active", CSSSelector::kPseudoActive},
     {"after", CSSSelector::kPseudoAfter},
+    {"any-link", CSSSelector::kPseudoAnyLink},
     {"backdrop", CSSSelector::kPseudoBackdrop},
     {"before", CSSSelector::kPseudoBefore},
     {"checked", CSSSelector::kPseudoChecked},
@@ -619,6 +621,7 @@
     case kPseudoValid:
     case kPseudoVertical:
     case kPseudoVisited:
+    case kPseudoWebkitAnyLink:
     case kPseudoWindowInactive:
       if (match_ != kPseudoClass)
         pseudo_type_ = kPseudoUnknown;
diff --git a/third_party/WebKit/Source/core/css/CSSSelector.h b/third_party/WebKit/Source/core/css/CSSSelector.h
index 23f4d7e..9edd6cb7 100644
--- a/third_party/WebKit/Source/core/css/CSSSelector.h
+++ b/third_party/WebKit/Source/core/css/CSSSelector.h
@@ -158,6 +158,7 @@
     kPseudoVisited,
     kPseudoAny,
     kPseudoAnyLink,
+    kPseudoWebkitAnyLink,
     kPseudoAutofill,
     kPseudoHover,
     kPseudoDrag,
diff --git a/third_party/WebKit/Source/core/css/RuleFeatureSet.cpp b/third_party/WebKit/Source/core/css/RuleFeatureSet.cpp
index 0f5607ac..12ccf5c 100644
--- a/third_party/WebKit/Source/core/css/RuleFeatureSet.cpp
+++ b/third_party/WebKit/Source/core/css/RuleFeatureSet.cpp
@@ -90,6 +90,7 @@
     case CSSSelector::kPseudoLink:
     case CSSSelector::kPseudoVisited:
     case CSSSelector::kPseudoAny:
+    case CSSSelector::kPseudoWebkitAnyLink:
     case CSSSelector::kPseudoAnyLink:
     case CSSSelector::kPseudoAutofill:
     case CSSSelector::kPseudoHover:
@@ -436,6 +437,7 @@
       case CSSSelector::kPseudoOnlyChild:
       case CSSSelector::kPseudoLink:
       case CSSSelector::kPseudoVisited:
+      case CSSSelector::kPseudoWebkitAnyLink:
       case CSSSelector::kPseudoAnyLink:
       case CSSSelector::kPseudoAutofill:
       case CSSSelector::kPseudoHover:
diff --git a/third_party/WebKit/Source/core/css/RuleSet.cpp b/third_party/WebKit/Source/core/css/RuleSet.cpp
index 205bacf..425e44e 100644
--- a/third_party/WebKit/Source/core/css/RuleSet.cpp
+++ b/third_party/WebKit/Source/core/css/RuleSet.cpp
@@ -119,6 +119,7 @@
         case CSSSelector::kPseudoCue:
         case CSSSelector::kPseudoLink:
         case CSSSelector::kPseudoVisited:
+        case CSSSelector::kPseudoWebkitAnyLink:
         case CSSSelector::kPseudoAnyLink:
         case CSSSelector::kPseudoFocus:
         case CSSSelector::kPseudoPlaceholder:
@@ -191,6 +192,7 @@
     case CSSSelector::kPseudoLink:
     case CSSSelector::kPseudoVisited:
     case CSSSelector::kPseudoAnyLink:
+    case CSSSelector::kPseudoWebkitAnyLink:
       link_pseudo_class_rules_.push_back(rule_data);
       return true;
     case CSSSelector::kPseudoFocus:
diff --git a/third_party/WebKit/Source/core/css/SelectorChecker.cpp b/third_party/WebKit/Source/core/css/SelectorChecker.cpp
index 77566c1..4a773c0 100644
--- a/third_party/WebKit/Source/core/css/SelectorChecker.cpp
+++ b/third_party/WebKit/Source/core/css/SelectorChecker.cpp
@@ -900,7 +900,12 @@
              ToHTMLFormControlElement(element).IsAutofilled();
     case CSSSelector::kPseudoAnyLink:
       UseCounter::Count(context.element->GetDocument(),
+                        WebFeature::kCSSSelectorPseudoAnyLink);
+      return element.IsLink();
+    case CSSSelector::kPseudoWebkitAnyLink:
+      UseCounter::Count(context.element->GetDocument(),
                         WebFeature::kCSSSelectorPseudoWebkitAnyLink);
+    // Fall through
     case CSSSelector::kPseudoLink:
       return element.IsLink();
     case CSSSelector::kPseudoVisited:
diff --git a/third_party/WebKit/Source/core/dom/VisitedLinkState.cpp b/third_party/WebKit/Source/core/dom/VisitedLinkState.cpp
index 8251512..5cff2b8e 100644
--- a/third_party/WebKit/Source/core/dom/VisitedLinkState.cpp
+++ b/third_party/WebKit/Source/core/dom/VisitedLinkState.cpp
@@ -71,6 +71,7 @@
         ToHTMLAnchorElement(node).InvalidateCachedVisitedLinkHash();
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoLink);
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoVisited);
+      ToElement(node).PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoAnyLink);
     }
     if (IsShadowHost(&node)) {
@@ -95,6 +96,7 @@
     if (node.IsLink() && LinkHashForElement(ToElement(node)) == link_hash) {
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoLink);
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoVisited);
+      ToElement(node).PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
       ToElement(node).PseudoStateChanged(CSSSelector::kPseudoAnyLink);
     }
     if (IsShadowHost(&node))
diff --git a/third_party/WebKit/Source/core/frame/UseCounterTest.cpp b/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
index 9741df4..47f53431 100644
--- a/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
+++ b/third_party/WebKit/Source/core/frame/UseCounterTest.cpp
@@ -57,6 +57,8 @@
   LocalFrame* GetFrame() { return &dummy_->GetFrame(); }
   void SetIsViewSource() { dummy_->GetDocument().SetIsViewSource(true); }
   void SetURL(const KURL& url) { dummy_->GetDocument().SetURL(url); }
+  Document& GetDocument() { return dummy_->GetDocument(); }
+
   template <typename T>
   void HistogramBasicTest(const std::string& histogram,
                           T item,
@@ -245,6 +247,22 @@
       [&](LocalFrame* frame) { use_counter.DidCommitLoad(frame); }, kSvgUrl);
 }
 
+TEST_F(UseCounterTest, CSSSelectorPseudoAnyLink) {
+  UseCounter use_counter;
+  WebFeature feature = WebFeature::kCSSSelectorPseudoAnyLink;
+  EXPECT_FALSE(use_counter.IsCounted(GetDocument(), feature));
+  use_counter.Count(GetDocument(), feature);
+  EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature));
+}
+
+TEST_F(UseCounterTest, CSSSelectorPseudoWebkitAnyLink) {
+  UseCounter use_counter;
+  WebFeature feature = WebFeature::kCSSSelectorPseudoWebkitAnyLink;
+  EXPECT_FALSE(use_counter.IsCounted(GetDocument(), feature));
+  use_counter.Count(GetDocument(), feature);
+  EXPECT_TRUE(use_counter.IsCounted(GetDocument(), feature));
+}
+
 TEST_F(UseCounterTest, InspectorDisablesMeasurement) {
   UseCounter use_counter;
 
diff --git a/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp b/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
index 5708793..85973ddd 100644
--- a/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLAnchorElement.cpp
@@ -203,6 +203,7 @@
     if (was_link || IsLink()) {
       PseudoStateChanged(CSSSelector::kPseudoLink);
       PseudoStateChanged(CSSSelector::kPseudoVisited);
+      PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
       PseudoStateChanged(CSSSelector::kPseudoAnyLink);
     }
     if (IsLink()) {
diff --git a/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp b/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp
index 91547ca..cc699f5 100644
--- a/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp
+++ b/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp
@@ -257,6 +257,7 @@
     DEFINE_STRING_MAPPING(PseudoLink)
     DEFINE_STRING_MAPPING(PseudoVisited)
     DEFINE_STRING_MAPPING(PseudoAny)
+    DEFINE_STRING_MAPPING(PseudoWebkitAnyLink)
     DEFINE_STRING_MAPPING(PseudoAnyLink)
     DEFINE_STRING_MAPPING(PseudoAutofill)
     DEFINE_STRING_MAPPING(PseudoHover)
diff --git a/third_party/WebKit/Source/core/svg/SVGAElement.cpp b/third_party/WebKit/Source/core/svg/SVGAElement.cpp
index 1eb1be6..0c7769d6 100644
--- a/third_party/WebKit/Source/core/svg/SVGAElement.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGAElement.cpp
@@ -88,6 +88,7 @@
     if (was_link || IsLink()) {
       PseudoStateChanged(CSSSelector::kPseudoLink);
       PseudoStateChanged(CSSSelector::kPseudoVisited);
+      PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
       PseudoStateChanged(CSSSelector::kPseudoAnyLink);
     }
     return;
diff --git a/third_party/WebKit/Source/modules/fetch/DEPS b/third_party/WebKit/Source/modules/fetch/DEPS
index 3cf197d..a7cc136 100644
--- a/third_party/WebKit/Source/modules/fetch/DEPS
+++ b/third_party/WebKit/Source/modules/fetch/DEPS
@@ -3,7 +3,6 @@
     "-modules",
     "+modules/ModulesExport.h",
     "+modules/fetch",
-    "+modules/credentialmanager",
     "+mojo/public/cpp/system/data_pipe.h",
     "+mojo/public/cpp/system/simple_watcher.h",
     "+services/network/public/interfaces",
diff --git a/third_party/WebKit/public/platform/web_feature.mojom b/third_party/WebKit/public/platform/web_feature.mojom
index ef5107e..e12a515 100644
--- a/third_party/WebKit/public/platform/web_feature.mojom
+++ b/third_party/WebKit/public/platform/web_feature.mojom
@@ -1772,6 +1772,7 @@
   kAudioWorkletNodeConstructor = 2263,
   kHTMLMediaElementEmptyLoadWithFutureData = 2264,
   kCSSValueDisplayContents = 2265,
+  kCSSSelectorPseudoAnyLink = 2266,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b6e0bd9..9aeb4dd 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17239,6 +17239,7 @@
   <int value="2263" label="AudioWorkletNodeConstructor"/>
   <int value="2264" label="HTMLMediaElementEmptyLoadWithFutureData"/>
   <int value="2265" label="CSSValueDisplayContents"/>
+  <int value="2266" label="CSSSelectorPseudoAnyLink"/>
 </enum>
 
 <enum name="FeedbackSource">
diff --git a/ui/aura/mus/window_port_mus.cc b/ui/aura/mus/window_port_mus.cc
index aedc2838..fcc808f4 100644
--- a/ui/aura/mus/window_port_mus.cc
+++ b/ui/aura/mus/window_port_mus.cc
@@ -91,6 +91,10 @@
                                                        touch_insets);
 }
 
+void WindowPortMus::SetHitTestMask(const base::Optional<gfx::Rect>& rect) {
+  window_tree_client_->SetHitTestMask(this, rect);
+}
+
 void WindowPortMus::Embed(
     ui::mojom::WindowTreeClientPtr client,
     uint32_t flags,
diff --git a/ui/aura/mus/window_port_mus.h b/ui/aura/mus/window_port_mus.h
index 732ca7b..318ea8fc 100644
--- a/ui/aura/mus/window_port_mus.h
+++ b/ui/aura/mus/window_port_mus.h
@@ -13,6 +13,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "components/viz/client/client_layer_tree_frame_sink.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "services/ui/public/interfaces/cursor/cursor.mojom.h"
@@ -85,6 +86,9 @@
   void SetExtendedHitRegionForChildren(const gfx::Insets& mouse_insets,
                                        const gfx::Insets& touch_insets);
 
+  // See description in mojom for details on this.
+  void SetHitTestMask(const base::Optional<gfx::Rect>& rect);
+
   // Embeds a new client in this Window. See WindowTreeClient::Embed() for
   // details on arguments.
   void Embed(ui::mojom::WindowTreeClientPtr client,
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc
index 3f26feb1..115ae85 100644
--- a/ui/aura/mus/window_tree_client.cc
+++ b/ui/aura/mus/window_tree_client.cc
@@ -361,6 +361,18 @@
   tree_->SetImeVisibility(window->server_id(), visible, std::move(state));
 }
 
+void WindowTreeClient::SetHitTestMask(
+    WindowMus* window,
+    const base::Optional<gfx::Rect>& mask_rect) {
+  base::Optional<gfx::Rect> out_rect = base::nullopt;
+  if (mask_rect) {
+    out_rect = gfx::ConvertRectToPixel(window->GetDeviceScaleFactor(),
+                                       mask_rect.value());
+  }
+
+  tree_->SetHitTestMask(window->server_id(), out_rect);
+}
+
 void WindowTreeClient::Embed(
     Window* window,
     ui::mojom::WindowTreeClientPtr client,
@@ -2240,20 +2252,6 @@
       additional_client_areas_in_pixel);
 }
 
-void WindowTreeClient::OnWindowTreeHostHitTestMaskWillChange(
-    WindowTreeHostMus* window_tree_host,
-    const base::Optional<gfx::Rect>& mask_rect) {
-  WindowMus* window = WindowMus::Get(window_tree_host->window());
-
-  base::Optional<gfx::Rect> out_rect = base::nullopt;
-  if (mask_rect) {
-    out_rect = gfx::ConvertRectToPixel(window->GetDeviceScaleFactor(),
-                                       mask_rect.value());
-  }
-
-  tree_->SetHitTestMask(window->server_id(), out_rect);
-}
-
 void WindowTreeClient::OnWindowTreeHostSetOpacity(
     WindowTreeHostMus* window_tree_host,
     float opacity) {
diff --git a/ui/aura/mus/window_tree_client.h b/ui/aura/mus/window_tree_client.h
index 1906821..c59b8a9 100644
--- a/ui/aura/mus/window_tree_client.h
+++ b/ui/aura/mus/window_tree_client.h
@@ -18,6 +18,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
@@ -146,6 +147,7 @@
   void SetImeVisibility(WindowMus* window,
                         bool visible,
                         ui::mojom::TextInputStatePtr state);
+  void SetHitTestMask(WindowMus* window, const base::Optional<gfx::Rect>& rect);
 
   // Embeds a new client in |window|. |flags| is a bitmask of the values defined
   // by kEmbedFlag*; 0 gives default behavior. |callback| is called to indicate
@@ -548,9 +550,6 @@
       WindowTreeHostMus* window_tree_host,
       const gfx::Insets& client_area,
       const std::vector<gfx::Rect>& additional_client_areas) override;
-  void OnWindowTreeHostHitTestMaskWillChange(
-      WindowTreeHostMus* window_tree_host,
-      const base::Optional<gfx::Rect>& mask_rect) override;
   void OnWindowTreeHostSetOpacity(WindowTreeHostMus* window_tree_host,
                                   float opacity) override;
   void OnWindowTreeHostDeactivateWindow(
diff --git a/ui/aura/mus/window_tree_host_mus.cc b/ui/aura/mus/window_tree_host_mus.cc
index 1552c67..6d3a3f2 100644
--- a/ui/aura/mus/window_tree_host_mus.cc
+++ b/ui/aura/mus/window_tree_host_mus.cc
@@ -130,10 +130,6 @@
                                                   additional_client_area);
 }
 
-void WindowTreeHostMus::SetHitTestMask(const base::Optional<gfx::Rect>& rect) {
-  delegate_->OnWindowTreeHostHitTestMaskWillChange(this, rect);
-}
-
 void WindowTreeHostMus::SetOpacity(float value) {
   delegate_->OnWindowTreeHostSetOpacity(this, value);
 }
diff --git a/ui/aura/mus/window_tree_host_mus.h b/ui/aura/mus/window_tree_host_mus.h
index 1457f35e..6896e19 100644
--- a/ui/aura/mus/window_tree_host_mus.h
+++ b/ui/aura/mus/window_tree_host_mus.h
@@ -52,10 +52,6 @@
   void SetClientArea(const gfx::Insets& insets,
                      const std::vector<gfx::Rect>& additional_client_area);
 
-  // Sets the hit test mask on the underlying mus window. Pass base::nullopt to
-  // clear.
-  void SetHitTestMask(const base::Optional<gfx::Rect>& rect);
-
   // Sets the opacity of the underlying mus window.
   void SetOpacity(float value);
 
diff --git a/ui/aura/mus/window_tree_host_mus_delegate.h b/ui/aura/mus/window_tree_host_mus_delegate.h
index d2deb47..e9131d8 100644
--- a/ui/aura/mus/window_tree_host_mus_delegate.h
+++ b/ui/aura/mus/window_tree_host_mus_delegate.h
@@ -37,11 +37,6 @@
       const gfx::Insets& client_area,
       const std::vector<gfx::Rect>& additional_client_areas) = 0;
 
-  // Called when the hit test mask is about to be cleared.
-  virtual void OnWindowTreeHostHitTestMaskWillChange(
-      WindowTreeHostMus* window_tree_host,
-      const base::Optional<gfx::Rect>& mask_rect) = 0;
-
   // Called when the opacity is changed client side.
   virtual void OnWindowTreeHostSetOpacity(WindowTreeHostMus* window_tree_host,
                                           float opacity) = 0;
diff --git a/ui/aura/mus/window_tree_host_mus_unittest.cc b/ui/aura/mus/window_tree_host_mus_unittest.cc
index 056c3e8f..3d7714e 100644
--- a/ui/aura/mus/window_tree_host_mus_unittest.cc
+++ b/ui/aura/mus/window_tree_host_mus_unittest.cc
@@ -5,6 +5,7 @@
 #include "ui/aura/mus/window_tree_host_mus.h"
 
 #include "base/memory/ptr_util.h"
+#include "ui/aura/mus/window_port_mus.h"
 #include "ui/aura/mus/window_tree_host_mus_init_params.h"
 #include "ui/aura/test/aura_mus_test_base.h"
 #include "ui/aura/test/mus/test_window_tree.h"
@@ -30,11 +31,12 @@
 
   EXPECT_FALSE(window_tree()->last_hit_test_mask().has_value());
   gfx::Rect mask(10, 10, 10, 10);
-  window_tree_host_mus->SetHitTestMask(mask);
+  WindowPortMus::Get(window_tree_host_mus->window())->SetHitTestMask(mask);
   ASSERT_TRUE(window_tree()->last_hit_test_mask().has_value());
   EXPECT_EQ(mask, window_tree()->last_hit_test_mask());
 
-  window_tree_host_mus->SetHitTestMask(base::nullopt);
+  WindowPortMus::Get(window_tree_host_mus->window())
+      ->SetHitTestMask(base::nullopt);
   ASSERT_FALSE(window_tree()->last_hit_test_mask().has_value());
 }
 
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 8d5bf470..b24db3f 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -253,7 +253,7 @@
 
 void DesktopWindowTreeHostMus::SendHitTestMaskToServer() {
   if (!native_widget_delegate_->HasHitTestMask()) {
-    SetHitTestMask(base::nullopt);
+    aura::WindowPortMus::Get(window())->SetHitTestMask(base::nullopt);
     return;
   }
 
@@ -262,7 +262,7 @@
   // TODO(jamescook): Use the full path for the mask.
   gfx::Rect mask_rect =
       gfx::ToEnclosingRect(gfx::SkRectToRectF(mask_path.getBounds()));
-  SetHitTestMask(mask_rect);
+  aura::WindowPortMus::Get(window())->SetHitTestMask(mask_rect);
 }
 
 bool DesktopWindowTreeHostMus::IsFocusClientInstalledOnFocusSynchronizer()
diff --git a/ui/wm/BUILD.gn b/ui/wm/BUILD.gn
index f16ed5a..4872ecd 100644
--- a/ui/wm/BUILD.gn
+++ b/ui/wm/BUILD.gn
@@ -125,6 +125,7 @@
     "core/compound_event_filter_unittest.cc",
     "core/coordinate_conversion_unittest.cc",
     "core/cursor_manager_unittest.cc",
+    "core/easy_resize_window_targeter_unittest.cc",
     "core/focus_controller_unittest.cc",
     "core/shadow_controller_unittest.cc",
     "core/shadow_unittest.cc",
diff --git a/ui/wm/core/easy_resize_window_targeter.cc b/ui/wm/core/easy_resize_window_targeter.cc
index 6986a19..9ac6839a 100644
--- a/ui/wm/core/easy_resize_window_targeter.cc
+++ b/ui/wm/core/easy_resize_window_targeter.cc
@@ -23,17 +23,65 @@
 // Returns an insets whose values are all negative or 0. Any positive value is
 // forced to 0.
 gfx::Insets InsetsWithOnlyNegativeValues(const gfx::Insets& insets) {
-  if (insets.top() > 0 || insets.left() > 0 || insets.right() > 0 ||
-      insets.bottom() > 0) {
-    // See TODO at call site.
-    NOTIMPLEMENTED_LOG_ONCE();
-  }
   return gfx::Insets(std::min(0, insets.top()), std::min(0, insets.left()),
                      std::min(0, insets.bottom()), std::min(0, insets.right()));
 }
 
+gfx::Insets InsetsWithOnlyPositiveValues(const gfx::Insets& insets) {
+  return gfx::Insets(std::max(0, insets.top()), std::max(0, insets.left()),
+                     std::max(0, insets.bottom()), std::max(0, insets.right()));
+}
+
 }  // namespace
 
+// HitMaskSetter is responsible for setting the hit-test mask on a Window.
+class EasyResizeWindowTargeter::HitMaskSetter : public aura::WindowObserver {
+ public:
+  explicit HitMaskSetter(aura::Window* window) : window_(window) {
+    window_->AddObserver(this);
+  }
+  ~HitMaskSetter() override {
+    if (window_) {
+      aura::WindowPortMus::Get(window_)->SetHitTestMask(base::nullopt);
+      window_->RemoveObserver(this);
+    }
+  }
+
+  void SetHitMaskInsets(const gfx::Insets& insets) {
+    if (insets == insets_)
+      return;
+
+    insets_ = insets;
+    ApplyHitTestMask();
+  }
+
+ private:
+  void ApplyHitTestMask() {
+    base::Optional<gfx::Rect> hit_test_mask(
+        gfx::Rect(window_->bounds().size()));
+    hit_test_mask->Inset(insets_);
+    aura::WindowPortMus::Get(window_)->SetHitTestMask(hit_test_mask);
+  }
+
+  // aura::WindowObserver:
+  void OnWindowDestroying(aura::Window* window) override {
+    window_->RemoveObserver(this);
+    window_ = nullptr;
+  }
+  void OnWindowBoundsChanged(aura::Window* window,
+                             const gfx::Rect& old_bounds,
+                             const gfx::Rect& new_bounds,
+                             ui::PropertyChangeReason reason) override {
+    ApplyHitTestMask();
+  }
+
+ private:
+  aura::Window* window_;
+  gfx::Insets insets_;
+
+  DISALLOW_COPY_AND_ASSIGN(HitMaskSetter);
+};
+
 EasyResizeWindowTargeter::EasyResizeWindowTargeter(
     aura::Window* container,
     const gfx::Insets& mouse_extend,
@@ -52,8 +100,6 @@
     return;
 
   // Mus only accepts 0 or negative values, force all values to fit that.
-  // TODO: figure out how best to deal with positive values, see
-  // http://crbug.com/775223
   const gfx::Insets effective_last_mouse_extend =
       InsetsWithOnlyNegativeValues(last_mouse_extend);
   const gfx::Insets effective_last_touch_extend =
@@ -62,14 +108,23 @@
       InsetsWithOnlyNegativeValues(mouse_extend());
   const gfx::Insets effective_touch_extend =
       InsetsWithOnlyNegativeValues(touch_extend());
-  if (effective_last_touch_extend == effective_touch_extend &&
-      effective_last_mouse_extend == effective_mouse_extend) {
-    return;
+  if (effective_last_touch_extend != effective_touch_extend ||
+      effective_last_mouse_extend != effective_mouse_extend) {
+    aura::WindowPortMus::Get(container_)
+        ->SetExtendedHitRegionForChildren(effective_mouse_extend,
+                                          effective_touch_extend);
   }
 
-  aura::WindowPortMus::Get(container_)
-      ->SetExtendedHitRegionForChildren(effective_mouse_extend,
-                                        effective_touch_extend);
+  // Positive values equate to a hit test mask.
+  const gfx::Insets positive_mouse_insets =
+      InsetsWithOnlyPositiveValues(mouse_extend());
+  if (positive_mouse_insets.IsEmpty()) {
+    hit_mask_setter_.reset();
+  } else {
+    if (!hit_mask_setter_)
+      hit_mask_setter_ = std::make_unique<HitMaskSetter>(container_);
+    hit_mask_setter_->SetHitMaskInsets(positive_mouse_insets);
+  }
 }
 
 bool EasyResizeWindowTargeter::EventLocationInsideBounds(
diff --git a/ui/wm/core/easy_resize_window_targeter.h b/ui/wm/core/easy_resize_window_targeter.h
index 100be11..d8ffa70 100644
--- a/ui/wm/core/easy_resize_window_targeter.h
+++ b/ui/wm/core/easy_resize_window_targeter.h
@@ -31,6 +31,8 @@
                    const gfx::Insets& last_touch_extend) override;
 
  private:
+  class HitMaskSetter;
+
   // aura::WindowTargeter:
   // Delegates to WindowTargeter's impl and prevents overriding in subclasses.
   bool EventLocationInsideBounds(aura::Window* target,
@@ -42,6 +44,8 @@
 
   aura::Window* container_;
 
+  std::unique_ptr<HitMaskSetter> hit_mask_setter_;
+
   DISALLOW_COPY_AND_ASSIGN(EasyResizeWindowTargeter);
 };
 
diff --git a/ui/wm/core/easy_resize_window_targeter_unittest.cc b/ui/wm/core/easy_resize_window_targeter_unittest.cc
new file mode 100644
index 0000000..092a17b
--- /dev/null
+++ b/ui/wm/core/easy_resize_window_targeter_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/wm/core/easy_resize_window_targeter.h"
+
+#include "ui/aura/mus/window_port_mus.h"
+#include "ui/aura/test/aura_mus_test_base.h"
+#include "ui/aura/test/mus/test_window_tree.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer_type.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace wm {
+
+namespace {
+
+class TestEasyResizeWindowTargeter : public EasyResizeWindowTargeter {
+ public:
+  explicit TestEasyResizeWindowTargeter(aura::Window* window)
+      : EasyResizeWindowTargeter(window, gfx::Insets(), gfx::Insets()) {}
+  ~TestEasyResizeWindowTargeter() override = default;
+
+  void SetInsets(const gfx::Insets& mouse_extend,
+                 const gfx::Insets& touch_extend) {
+    EasyResizeWindowTargeter::SetInsets(mouse_extend, touch_extend);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestEasyResizeWindowTargeter);
+};
+
+}  // namespace
+
+using EasyResizeWindowTargeterTest = aura::test::AuraMusClientTestBase;
+
+TEST_F(EasyResizeWindowTargeterTest, SetHitTestMask) {
+  aura::Window window(nullptr);
+  window.Init(ui::LAYER_NOT_DRAWN);
+  TestEasyResizeWindowTargeter window_targeter(&window);
+  const gfx::Rect bounds1 = gfx::Rect(10, 20, 200, 300);
+  window.SetBounds(bounds1);
+  const gfx::Insets insets1(1, 2, 3, 4);
+  window_targeter.SetInsets(insets1, insets1);
+  ASSERT_TRUE(window_tree()->last_hit_test_mask().has_value());
+  EXPECT_EQ(gfx::Rect(insets1.left(), insets1.top(),
+                      bounds1.width() - insets1.width(),
+                      bounds1.height() - insets1.height()),
+            *window_tree()->last_hit_test_mask());
+
+  // Adjusting the bounds should trigger resetting the mask.
+  const gfx::Rect bounds2 = gfx::Rect(10, 20, 300, 400);
+  window.SetBounds(bounds2);
+  ASSERT_TRUE(window_tree()->last_hit_test_mask().has_value());
+  EXPECT_EQ(gfx::Rect(insets1.left(), insets1.top(),
+                      bounds2.width() - insets1.width(),
+                      bounds2.height() - insets1.height()),
+            *window_tree()->last_hit_test_mask());
+
+  // Empty insets should reset the mask.
+  window_targeter.SetInsets(gfx::Insets(), gfx::Insets());
+  EXPECT_FALSE(window_tree()->last_hit_test_mask().has_value());
+
+  const gfx::Insets insets2(-1, 3, 4, 5);
+  const gfx::Insets effective_insets2(0, 3, 4, 5);
+  window_targeter.SetInsets(insets2, insets2);
+  ASSERT_TRUE(window_tree()->last_hit_test_mask().has_value());
+  EXPECT_EQ(gfx::Rect(effective_insets2.left(), effective_insets2.top(),
+                      bounds2.width() - effective_insets2.width(),
+                      bounds2.height() - effective_insets2.height()),
+            *window_tree()->last_hit_test_mask());
+}
+
+}  // namespace wm