Add MediaAccessHandler for getDisplayMedia()

This CL adds a new permission handler for getDisplayMedia(). It uses
DesktopMediaPickerFactoryImpl as common implementation to trigger the
same picker UI as extensions. It also adds unit test that exercises
the new code path.

Bug: 326740
Change-Id: I3007124226d2b017ba06877a1bbcc87f3bc2f9a3
Reviewed-on: https://chromium-review.googlesource.com/1163533
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Sergey Ulanov <sergeyu@chromium.org>
Reviewed-by: Xiangjun Zhang <xjz@chromium.org>
Reviewed-by: Weiyong Yao <braveyao@chromium.org>
Commit-Queue: Emircan Uysaler <emircan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582835}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 8a3799c..fdcbc71 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2594,10 +2594,16 @@
       "lifetime/browser_close_manager.h",
       "lifetime/termination_notification.cc",
       "lifetime/termination_notification.h",
+      "media/capture_access_handler_base.cc",
+      "media/capture_access_handler_base.h",
       "media/unified_autoplay_config.cc",
       "media/unified_autoplay_config.h",
+      "media/webrtc/desktop_capture_devices_util.cc",
+      "media/webrtc/desktop_capture_devices_util.h",
       "media/webrtc/desktop_media_picker_factory_impl.cc",
       "media/webrtc/desktop_media_picker_factory_impl.h",
+      "media/webrtc/display_media_access_handler.cc",
+      "media/webrtc/display_media_access_handler.h",
       "media/webrtc/tab_desktop_media_list.cc",
       "media/webrtc/tab_desktop_media_list.h",
       "media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc",
@@ -3657,8 +3663,6 @@
       "guest_view/web_view/chrome_web_view_permission_helper_delegate.h",
       "guest_view/web_view/context_menu_content_type_web_view.cc",
       "guest_view/web_view/context_menu_content_type_web_view.h",
-      "media/capture_access_handler_base.cc",
-      "media/capture_access_handler_base.h",
       "media/cast_transport_host_filter.cc",
       "media/cast_transport_host_filter.h",
       "media/extension_media_access_handler.cc",
diff --git a/chrome/browser/media/capture_access_handler_base.cc b/chrome/browser/media/capture_access_handler_base.cc
index 7b6fd50..194fb46 100644
--- a/chrome/browser/media/capture_access_handler_base.cc
+++ b/chrome/browser/media/capture_access_handler_base.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/capture_access_handler_base.h"
 
+#include <string>
 #include <utility>
 
 #include "base/strings/string_number_conversions.h"
@@ -26,7 +27,7 @@
   // Extensions control the routing of the captured MediaStream content.
   // Therefore, only built-in extensions (and certain whitelisted ones) can be
   // trusted to set-up secure links.
-  bool is_extension_trusted;
+  bool is_trusted;
 
   // This is true only if all connected video sinks are reported secure.
   bool is_capturing_link_secure;
@@ -39,9 +40,9 @@
 void CaptureAccessHandlerBase::AddCaptureSession(int render_process_id,
                                                  int render_frame_id,
                                                  int page_request_id,
-                                                 bool is_extension_trusted) {
+                                                 bool is_trusted) {
   Session session = {render_process_id, render_frame_id, page_request_id,
-                     is_extension_trusted, true};
+                     is_trusted, true};
   sessions_.push_back(session);
 }
 
@@ -74,7 +75,8 @@
     content::MediaRequestState state) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if ((stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) &&
-      (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE))
+      (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE) &&
+      (stream_type != content::MEDIA_DISPLAY_VIDEO_CAPTURE))
     return;
 
   if (state == content::MEDIA_REQUEST_STATE_DONE) {
@@ -99,32 +101,36 @@
 void CaptureAccessHandlerBase::UpdateExtensionTrusted(
     const content::MediaStreamRequest& request,
     const extensions::Extension* extension) {
-  bool is_extension_trusted =
-      MediaCaptureDevicesDispatcher::IsOriginForCasting(
-          request.security_origin) ||
-      IsExtensionWhitelistedForScreenCapture(extension) ||
-      IsBuiltInExtension(request.security_origin);
+  const bool is_trusted = MediaCaptureDevicesDispatcher::IsOriginForCasting(
+                              request.security_origin) ||
+                          IsExtensionWhitelistedForScreenCapture(extension) ||
+                          IsBuiltInExtension(request.security_origin);
+  UpdateTrusted(request, is_trusted);
+}
 
+void CaptureAccessHandlerBase::UpdateTrusted(
+    const content::MediaStreamRequest& request,
+    bool is_trusted) {
   std::list<CaptureAccessHandlerBase::Session>::iterator it =
       FindSession(request.render_process_id, request.render_frame_id,
                   request.page_request_id);
   if (it != sessions_.end()) {
-    it->is_extension_trusted = is_extension_trusted;
-    DVLOG(2) << "CaptureAccessHandlerBase::UpdateExtensionTrusted"
+    it->is_trusted = is_trusted;
+    DVLOG(2) << "CaptureAccessHandlerBase::UpdateTrusted"
              << " render_process_id: " << request.render_process_id
              << " render_frame_id: " << request.render_frame_id
-             << "page_request_id: " << request.page_request_id
-             << " is_extension_trusted: " << is_extension_trusted;
+             << " page_request_id: " << request.page_request_id
+             << " is_trusted: " << is_trusted;
     return;
   }
 
   AddCaptureSession(request.render_process_id, request.render_frame_id,
-                    request.page_request_id, is_extension_trusted);
-  DVLOG(2) << "Add new session while UpdateExtensionTrusted"
+                    request.page_request_id, is_trusted);
+  DVLOG(2) << "Add new session while UpdateTrusted"
            << " render_process_id: " << request.render_process_id
            << " render_frame_id: " << request.render_frame_id
            << " page_request_id: " << request.page_request_id
-           << " is_extension_trusted: " << is_extension_trusted;
+           << " is_trusted: " << is_trusted;
 }
 
 bool CaptureAccessHandlerBase::IsInsecureCapturingInProgress(
@@ -136,7 +142,7 @@
     if (session.render_process_id != render_process_id ||
         session.render_frame_id != render_frame_id)
       continue;
-    if (!session.is_extension_trusted || !session.is_capturing_link_secure)
+    if (!session.is_trusted || !session.is_capturing_link_secure)
       return true;
   }
   return false;
diff --git a/chrome/browser/media/capture_access_handler_base.h b/chrome/browser/media/capture_access_handler_base.h
index 7e22fe9..0658c0f 100644
--- a/chrome/browser/media/capture_access_handler_base.h
+++ b/chrome/browser/media/capture_access_handler_base.h
@@ -30,12 +30,12 @@
   // deemed secure if all connected video sinks are reported secure and the
   // connections to the sinks are being managed by a trusted extension.
   bool IsInsecureCapturingInProgress(int render_process_id,
-                                     int render_frame_id);
+                                     int render_frame_id) override;
 
   void UpdateCapturingLinkSecured(int render_process_id,
                                   int render_frame_id,
                                   int page_request_id,
-                                  bool is_secure);
+                                  bool is_secure) override;
 
  protected:
   static bool IsExtensionWhitelistedForScreenCapture(
@@ -46,13 +46,16 @@
   void UpdateExtensionTrusted(const content::MediaStreamRequest& request,
                               const extensions::Extension* extension);
 
+  void UpdateTrusted(const content::MediaStreamRequest& request,
+                     bool is_trusted);
+
  private:
   struct Session;
 
   void AddCaptureSession(int render_process_id,
                          int render_frame_id,
                          int page_request_id,
-                         bool is_extension_trusted);
+                         bool is_trusted);
 
   void RemoveCaptureSession(int render_process_id,
                             int render_frame_id,
diff --git a/chrome/browser/media/media_access_handler.cc b/chrome/browser/media/media_access_handler.cc
index f822eba..318aa88b 100644
--- a/chrome/browser/media/media_access_handler.cc
+++ b/chrome/browser/media/media_access_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/media_access_handler.h"
 
+#include <memory>
 #include <utility>
 
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
@@ -11,6 +12,11 @@
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/web_contents.h"
 
+bool MediaAccessHandler::IsInsecureCapturingInProgress(int render_process_id,
+                                                       int render_frame_id) {
+  return false;
+}
+
 // static
 void MediaAccessHandler::CheckDevicesAndRunCallback(
     content::WebContents* web_contents,
diff --git a/chrome/browser/media/media_access_handler.h b/chrome/browser/media/media_access_handler.h
index af5b9cc..fd93a9a 100644
--- a/chrome/browser/media/media_access_handler.h
+++ b/chrome/browser/media/media_access_handler.h
@@ -29,6 +29,7 @@
   virtual bool SupportsStreamType(content::WebContents* web_contents,
                                   const content::MediaStreamType type,
                                   const extensions::Extension* extension) = 0;
+
   // Check media access permission. |extension| is set to NULL if request was
   // made from a drive-by page.
   virtual bool CheckMediaAccessPermission(
@@ -36,12 +37,14 @@
       const GURL& security_origin,
       content::MediaStreamType type,
       const extensions::Extension* extension) = 0;
+
   // Process media access requests. |extension| is set to NULL if request was
   // made from a drive-by page.
   virtual void HandleRequest(content::WebContents* web_contents,
                              const content::MediaStreamRequest& request,
                              content::MediaResponseCallback callback,
                              const extensions::Extension* extension) = 0;
+
   // Update media request state. Called on UI thread.
   virtual void UpdateMediaRequestState(int render_process_id,
                                        int render_frame_id,
@@ -49,6 +52,18 @@
                                        content::MediaStreamType stream_type,
                                        content::MediaRequestState state) {}
 
+  // Return true if there is any ongoing insecured capturing. The capturing is
+  // deemed secure if all connected video sinks are reported secure and the
+  // connections to the sinks are being managed by a trusted source.
+  virtual bool IsInsecureCapturingInProgress(int render_process_id,
+                                             int render_frame_id);
+
+  //  Update any ongoing insecured capturing state.
+  virtual void UpdateCapturingLinkSecured(int render_process_id,
+                                          int render_frame_id,
+                                          int page_request_id,
+                                          bool is_secure) {}
+
  protected:
   // Helper function for derived classes which takes in whether audio/video
   // permissions are allowed and queries for the requested devices, running the
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
index 09a3b40..b4d1196 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -4,13 +4,15 @@
 
 #include "chrome/browser/media/webrtc/desktop_capture_access_handler.h"
 
-#include <utility>
+#include <memory>
+#include <string>
 
 #include "base/command_line.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/ui/browser.h"
@@ -21,7 +23,6 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/desktop_media_id.h"
 #include "content/public/browser/desktop_streams_registry.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -33,7 +34,6 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/switches.h"
-#include "media/audio/audio_device_description.h"
 #include "net/base/url_util.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -73,136 +73,6 @@
             extension->location() == extensions::Manifest::EXTERNAL_COMPONENT));
 }
 
-base::string16 GetStopSharingUIString(
-    const base::string16& application_title,
-    const base::string16& registered_extension_name,
-    bool capture_audio,
-    content::DesktopMediaID::Type capture_type) {
-  if (!capture_audio) {
-    if (application_title == registered_extension_name) {
-      switch (capture_type) {
-        case content::DesktopMediaID::TYPE_SCREEN:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title);
-        case content::DesktopMediaID::TYPE_WINDOW:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title);
-        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title);
-        case content::DesktopMediaID::TYPE_NONE:
-          NOTREACHED();
-      }
-    } else {
-      switch (capture_type) {
-        case content::DesktopMediaID::TYPE_SCREEN:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
-              registered_extension_name, application_title);
-        case content::DesktopMediaID::TYPE_WINDOW:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
-              registered_extension_name, application_title);
-        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
-              registered_extension_name, application_title);
-        case content::DesktopMediaID::TYPE_NONE:
-          NOTREACHED();
-      }
-    }
-  } else {  // The case with audio
-    if (application_title == registered_extension_name) {
-      switch (capture_type) {
-        case content::DesktopMediaID::TYPE_SCREEN:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
-              application_title);
-        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
-              application_title);
-        case content::DesktopMediaID::TYPE_NONE:
-        case content::DesktopMediaID::TYPE_WINDOW:
-          NOTREACHED();
-      }
-    } else {
-      switch (capture_type) {
-        case content::DesktopMediaID::TYPE_SCREEN:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
-              registered_extension_name, application_title);
-        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
-          return l10n_util::GetStringFUTF16(
-              IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
-              registered_extension_name, application_title);
-        case content::DesktopMediaID::TYPE_NONE:
-        case content::DesktopMediaID::TYPE_WINDOW:
-          NOTREACHED();
-      }
-    }
-  }
-  return base::string16();
-}
-// Helper to get list of media stream devices for desktop capture in |devices|.
-// Registers to display notification if |display_notification| is true.
-// Returns an instance of MediaStreamUI to be passed to content layer.
-std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
-    content::WebContents* web_contents,
-    content::MediaStreamDevices* devices,
-    content::DesktopMediaID media_id,
-    bool capture_audio,
-    bool disable_local_echo,
-    bool display_notification,
-    const base::string16& application_title,
-    const base::string16& registered_extension_name) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  DVLOG(2) << __func__ << ": media_id " << media_id.ToString()
-           << ", capture_audio " << capture_audio << ", disable_local_echo "
-           << disable_local_echo << ", display_notification "
-           << display_notification << ", application_title "
-           << application_title << ", extension_name "
-           << registered_extension_name;
-
-  // Add selected desktop source to the list.
-  devices->push_back(
-      content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
-                                 media_id.ToString(), media_id.ToString()));
-  if (capture_audio) {
-    if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) {
-      content::WebContentsMediaCaptureId web_id = media_id.web_contents_id;
-      web_id.disable_local_echo = disable_local_echo;
-      devices->push_back(
-          content::MediaStreamDevice(content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
-                                     web_id.ToString(), "Tab audio"));
-    } else if (disable_local_echo) {
-      // Use the special loopback device ID for system audio capture.
-      devices->push_back(content::MediaStreamDevice(
-          content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
-          media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
-          "System Audio"));
-    } else {
-      // Use the special loopback device ID for system audio capture.
-      devices->push_back(content::MediaStreamDevice(
-          content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
-          media::AudioDeviceDescription::kLoopbackInputDeviceId,
-          "System Audio"));
-    }
-  }
-
-  // If required, register to display the notification for stream capture.
-  std::unique_ptr<ScreenCaptureNotificationUI> notification_ui;
-  if (display_notification) {
-    notification_ui = ScreenCaptureNotificationUI::Create(
-        GetStopSharingUIString(application_title, registered_extension_name,
-                               capture_audio, media_id.type));
-  }
-
-  return MediaCaptureDevicesDispatcher::GetInstance()
-      ->GetMediaStreamCaptureIndicator()
-      ->RegisterMediaStream(web_contents, *devices, std::move(notification_ui));
-}
 
 #if !defined(OS_ANDROID)
 // Find browser or app window from a given |web_contents|.
@@ -341,7 +211,9 @@
       const bool display_notification = ShouldDisplayNotification(extension);
 
       ui = GetDevicesForDesktopCapture(
-          web_contents, &devices, screen_id, capture_audio,
+          web_contents, &devices, screen_id,
+          content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+          content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE, capture_audio,
           request.disable_local_echo, display_notification, application_title,
           application_title);
       DCHECK(!devices.empty());
@@ -464,6 +336,8 @@
   const bool display_notification = ShouldDisplayNotification(extension);
 
   ui = GetDevicesForDesktopCapture(web_contents, &devices, media_id,
+                                   content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE,
+                                   content::MEDIA_GUM_DESKTOP_AUDIO_CAPTURE,
                                    capture_audio, request.disable_local_echo,
                                    display_notification,
                                    GetApplicationTitle(web_contents, extension),
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.h b/chrome/browser/media/webrtc/desktop_capture_access_handler.h
index 6d3bd7bb..c5991fb7 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.h
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_ACCESS_HANDLER_H_
 
 #include <list>
+#include <utility>
 
 #include "chrome/browser/media/capture_access_handler_base.h"
 #include "chrome/browser/media/media_access_handler.h"
@@ -14,7 +15,9 @@
 class Extension;
 }
 
-// MediaAccessHandler for DesktopCapture API.
+// MediaAccessHandler for DesktopCapture API requests that originate from
+// getUserMedia() calls. Note that getDisplayMedia() calls are handled in
+// DisplayMediaAccessHandler.
 class DesktopCaptureAccessHandler : public CaptureAccessHandlerBase {
  public:
   DesktopCaptureAccessHandler();
diff --git a/chrome/browser/media/webrtc/desktop_capture_devices_util.cc b/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
new file mode 100644
index 0000000..ab79f5a
--- /dev/null
+++ b/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
@@ -0,0 +1,149 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+
+#include <string>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/ui/screen_capture_notification_ui.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "media/audio/audio_device_description.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+base::string16 GetStopSharingUIString(
+    const base::string16& application_title,
+    const base::string16& registered_extension_name,
+    bool capture_audio,
+    content::DesktopMediaID::Type capture_type) {
+  if (!capture_audio) {
+    if (application_title == registered_extension_name) {
+      switch (capture_type) {
+        case content::DesktopMediaID::TYPE_SCREEN:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title);
+        case content::DesktopMediaID::TYPE_WINDOW:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title);
+        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title);
+        case content::DesktopMediaID::TYPE_NONE:
+          NOTREACHED();
+      }
+    } else {
+      switch (capture_type) {
+        case content::DesktopMediaID::TYPE_SCREEN:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+              registered_extension_name, application_title);
+        case content::DesktopMediaID::TYPE_WINDOW:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+              registered_extension_name, application_title);
+        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
+              registered_extension_name, application_title);
+        case content::DesktopMediaID::TYPE_NONE:
+          NOTREACHED();
+      }
+    }
+  } else {  // The case with audio
+    if (application_title == registered_extension_name) {
+      switch (capture_type) {
+        case content::DesktopMediaID::TYPE_SCREEN:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+              application_title);
+        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT,
+              application_title);
+        case content::DesktopMediaID::TYPE_NONE:
+        case content::DesktopMediaID::TYPE_WINDOW:
+          NOTREACHED();
+      }
+    } else {
+      switch (capture_type) {
+        case content::DesktopMediaID::TYPE_SCREEN:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+              registered_extension_name, application_title);
+        case content::DesktopMediaID::TYPE_WEB_CONTENTS:
+          return l10n_util::GetStringFUTF16(
+              IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED,
+              registered_extension_name, application_title);
+        case content::DesktopMediaID::TYPE_NONE:
+        case content::DesktopMediaID::TYPE_WINDOW:
+          NOTREACHED();
+      }
+    }
+  }
+  return base::string16();
+}
+
+}  // namespace
+
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+    content::WebContents* web_contents,
+    content::MediaStreamDevices* devices,
+    content::DesktopMediaID media_id,
+    content::MediaStreamType devices_video_type,
+    content::MediaStreamType devices_audio_type,
+    bool capture_audio,
+    bool disable_local_echo,
+    bool display_notification,
+    const base::string16& application_title,
+    const base::string16& registered_extension_name) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  DVLOG(2) << __func__ << ": media_id " << media_id.ToString()
+           << ", capture_audio " << capture_audio << ", disable_local_echo "
+           << disable_local_echo << ", display_notification "
+           << display_notification << ", application_title "
+           << application_title << ", extension_name "
+           << registered_extension_name;
+
+  // Add selected desktop source to the list.
+  devices->push_back(content::MediaStreamDevice(
+      devices_video_type, media_id.ToString(), media_id.ToString()));
+  if (capture_audio) {
+    if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) {
+      content::WebContentsMediaCaptureId web_id = media_id.web_contents_id;
+      web_id.disable_local_echo = disable_local_echo;
+      devices->push_back(content::MediaStreamDevice(
+          devices_audio_type, web_id.ToString(), "Tab audio"));
+    } else if (disable_local_echo) {
+      // Use the special loopback device ID for system audio capture.
+      devices->push_back(content::MediaStreamDevice(
+          devices_audio_type,
+          media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
+          "System Audio"));
+    } else {
+      // Use the special loopback device ID for system audio capture.
+      devices->push_back(content::MediaStreamDevice(
+          devices_audio_type,
+          media::AudioDeviceDescription::kLoopbackInputDeviceId,
+          "System Audio"));
+    }
+  }
+
+  // If required, register to display the notification for stream capture.
+  std::unique_ptr<ScreenCaptureNotificationUI> notification_ui;
+  if (display_notification) {
+    notification_ui = ScreenCaptureNotificationUI::Create(
+        GetStopSharingUIString(application_title, registered_extension_name,
+                               capture_audio, media_id.type));
+  }
+
+  return MediaCaptureDevicesDispatcher::GetInstance()
+      ->GetMediaStreamCaptureIndicator()
+      ->RegisterMediaStream(web_contents, *devices, std::move(notification_ui));
+}
diff --git a/chrome/browser/media/webrtc/desktop_capture_devices_util.h b/chrome/browser/media/webrtc/desktop_capture_devices_util.h
new file mode 100644
index 0000000..6378781
--- /dev/null
+++ b/chrome/browser/media/webrtc/desktop_capture_devices_util.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
+
+#include <memory>
+
+#include "base/strings/string_util.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/media_stream_request.h"
+
+// Helper to get list of media stream devices for desktop capture in |devices|.
+// Registers to display notification if |display_notification| is true.
+// Returns an instance of MediaStreamUI to be passed to content layer.
+std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
+    content::WebContents* web_contents,
+    content::MediaStreamDevices* devices,
+    content::DesktopMediaID media_id,
+    content::MediaStreamType devices_video_type,
+    content::MediaStreamType devices_audio_type,
+    bool capture_audio,
+    bool disable_local_echo,
+    bool display_notification,
+    const base::string16& application_title,
+    const base::string16& registered_extension_name);
+
+#endif  // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
new file mode 100644
index 0000000..3af4185
--- /dev/null
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -0,0 +1,221 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
+#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
+#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/desktop_capture.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+
+// Holds pending request information so that we display one picker UI at a time
+// for each content::WebContents.
+struct DisplayMediaAccessHandler::PendingAccessRequest {
+  PendingAccessRequest(std::unique_ptr<DesktopMediaPicker> picker,
+                       const content::MediaStreamRequest& request,
+                       content::MediaResponseCallback callback)
+      : picker(std::move(picker)),
+        request(request),
+        callback(std::move(callback)) {}
+  ~PendingAccessRequest() = default;
+
+  std::unique_ptr<DesktopMediaPicker> picker;
+  content::MediaStreamRequest request;
+  content::MediaResponseCallback callback;
+};
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler()
+    : picker_factory_(new DesktopMediaPickerFactoryImpl()) {
+  AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::DisplayMediaAccessHandler(
+    std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+    bool display_notification)
+    : display_notification_(display_notification),
+      picker_factory_(std::move(picker_factory)) {
+  AddNotificationObserver();
+}
+
+DisplayMediaAccessHandler::~DisplayMediaAccessHandler() = default;
+
+bool DisplayMediaAccessHandler::SupportsStreamType(
+    content::WebContents* web_contents,
+    const content::MediaStreamType stream_type,
+    const extensions::Extension* extension) {
+  return stream_type == content::MEDIA_DISPLAY_VIDEO_CAPTURE;
+}
+
+bool DisplayMediaAccessHandler::CheckMediaAccessPermission(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& security_origin,
+    content::MediaStreamType type,
+    const extensions::Extension* extension) {
+  return false;
+}
+
+void DisplayMediaAccessHandler::HandleRequest(
+    content::WebContents* web_contents,
+    const content::MediaStreamRequest& request,
+    content::MediaResponseCallback callback,
+    const extensions::Extension* extension) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  std::unique_ptr<DesktopMediaPicker> picker = picker_factory_->CreatePicker();
+  if (!picker) {
+    std::move(callback).Run(content::MediaStreamDevices(),
+                            content::MEDIA_DEVICE_INVALID_STATE, nullptr);
+    return;
+  }
+
+  RequestsQueue& queue = pending_requests_[web_contents];
+  queue.push_back(std::make_unique<PendingAccessRequest>(
+      std::move(picker), request, std::move(callback)));
+  // If this is the only request then pop picker UI.
+  if (queue.size() == 1)
+    ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::UpdateMediaRequestState(
+    int render_process_id,
+    int render_frame_id,
+    int page_request_id,
+    content::MediaStreamType stream_type,
+    content::MediaRequestState state) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (state != content::MEDIA_REQUEST_STATE_DONE &&
+      state != content::MEDIA_REQUEST_STATE_CLOSING) {
+    return;
+  }
+
+  if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+    DeletePendingAccessRequest(render_process_id, render_frame_id,
+                               page_request_id);
+  }
+  CaptureAccessHandlerBase::UpdateMediaRequestState(
+      render_process_id, render_frame_id, page_request_id, stream_type, state);
+
+  // This method only gets called with the above checked states when all
+  // requests are to be canceled. Therefore, we don't need to process the
+  // next queued request.
+}
+
+void DisplayMediaAccessHandler::ProcessQueuedAccessRequest(
+    const RequestsQueue& queue,
+    content::WebContents* web_contents) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  const PendingAccessRequest& pending_request = *queue.front();
+  UpdateTrusted(pending_request.request, false /* is_trusted */);
+
+  std::vector<content::DesktopMediaID::Type> media_types = {
+      content::DesktopMediaID::TYPE_SCREEN,
+      content::DesktopMediaID::TYPE_WINDOW,
+      content::DesktopMediaID::TYPE_WEB_CONTENTS};
+  auto source_lists = picker_factory_->CreateMediaList(media_types);
+
+  DesktopMediaPicker::DoneCallback done_callback =
+      base::BindRepeating(&DisplayMediaAccessHandler::OnPickerDialogResults,
+                          base::Unretained(this), web_contents);
+  DesktopMediaPicker::Params picker_params;
+  picker_params.web_contents = web_contents;
+  gfx::NativeWindow parent_window = web_contents->GetTopLevelNativeWindow();
+  picker_params.context = parent_window;
+  picker_params.parent = parent_window;
+  picker_params.app_name = web_contents->GetTitle();
+  picker_params.target_name = web_contents->GetTitle();
+  picker_params.request_audio = false;
+  pending_request.picker->Show(picker_params, std::move(source_lists),
+                               done_callback);
+}
+
+void DisplayMediaAccessHandler::OnPickerDialogResults(
+    content::WebContents* web_contents,
+    content::DesktopMediaID media_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(web_contents);
+
+  auto it = pending_requests_.find(web_contents);
+  if (it == pending_requests_.end())
+    return;
+  RequestsQueue& queue = it->second;
+  if (queue.empty()) {
+    // UpdateMediaRequestState() called with MEDIA_REQUEST_STATE_CLOSING. Don't
+    // need to do anything.
+    return;
+  }
+
+  PendingAccessRequest& pending_request = *queue.front();
+  content::MediaStreamDevices devices;
+  content::MediaStreamRequestResult request_result =
+      content::MEDIA_DEVICE_PERMISSION_DENIED;
+  std::unique_ptr<content::MediaStreamUI> ui;
+  if (media_id.is_null()) {
+    request_result = content::MEDIA_DEVICE_PERMISSION_DENIED;
+  } else {
+    request_result = content::MEDIA_DEVICE_OK;
+    ui = GetDevicesForDesktopCapture(
+        web_contents, &devices, media_id, content::MEDIA_DISPLAY_VIDEO_CAPTURE,
+        content::MEDIA_NO_SERVICE, false /* capture_audio */,
+        false /* disable_local_echo */, display_notification_,
+        web_contents->GetTitle(), web_contents->GetTitle());
+  }
+
+  std::move(pending_request.callback)
+      .Run(devices, request_result, std::move(ui));
+  queue.pop_front();
+
+  if (!queue.empty())
+    ProcessQueuedAccessRequest(queue, web_contents);
+}
+
+void DisplayMediaAccessHandler::AddNotificationObserver() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  notifications_registrar_.Add(this,
+                               content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+                               content::NotificationService::AllSources());
+}
+
+void DisplayMediaAccessHandler::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
+
+  pending_requests_.erase(content::Source<content::WebContents>(source).ptr());
+}
+
+void DisplayMediaAccessHandler::DeletePendingAccessRequest(
+    int render_process_id,
+    int render_frame_id,
+    int page_request_id) {
+  for (auto& queue_it : pending_requests_) {
+    RequestsQueue& queue = queue_it.second;
+    for (auto it = queue.begin(); it != queue.end(); ++it) {
+      const PendingAccessRequest& pending_request = **it;
+      if (pending_request.request.render_process_id == render_process_id &&
+          pending_request.request.render_frame_id == render_frame_id &&
+          pending_request.request.page_request_id == page_request_id) {
+        queue.erase(it);
+        return;
+      }
+    }
+  }
+}
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.h b/chrome/browser/media/webrtc/display_media_access_handler.h
new file mode 100644
index 0000000..daa4146
--- /dev/null
+++ b/chrome/browser/media/webrtc/display_media_access_handler.h
@@ -0,0 +1,88 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
+
+#include <memory>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "chrome/browser/media/capture_access_handler_base.h"
+#include "chrome/browser/media/media_access_handler.h"
+#include "chrome/browser/media/webrtc/desktop_media_list.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker.h"
+#include "chrome/browser/media/webrtc/desktop_media_picker_factory.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+namespace extensions {
+class Extension;
+}
+
+// MediaAccessHandler for getDisplayMedia API, see
+// https://w3c.github.io/mediacapture-screen-share.
+class DisplayMediaAccessHandler : public CaptureAccessHandlerBase,
+                                  public content::NotificationObserver {
+ public:
+  DisplayMediaAccessHandler();
+  DisplayMediaAccessHandler(
+      std::unique_ptr<DesktopMediaPickerFactory> picker_factory,
+      bool display_notification);
+  ~DisplayMediaAccessHandler() override;
+
+  // MediaAccessHandler implementation.
+  bool SupportsStreamType(content::WebContents* web_contents,
+                          const content::MediaStreamType stream_type,
+                          const extensions::Extension* extension) override;
+  bool CheckMediaAccessPermission(
+      content::RenderFrameHost* render_frame_host,
+      const GURL& security_origin,
+      content::MediaStreamType type,
+      const extensions::Extension* extension) override;
+  void HandleRequest(content::WebContents* web_contents,
+                     const content::MediaStreamRequest& request,
+                     content::MediaResponseCallback callback,
+                     const extensions::Extension* extension) override;
+  void UpdateMediaRequestState(int render_process_id,
+                               int render_frame_id,
+                               int page_request_id,
+                               content::MediaStreamType stream_type,
+                               content::MediaRequestState state) override;
+
+ private:
+  friend class DisplayMediaAccessHandlerTest;
+
+  struct PendingAccessRequest;
+  using RequestsQueue =
+      base::circular_deque<std::unique_ptr<PendingAccessRequest>>;
+  using RequestsQueues = base::flat_map<content::WebContents*, RequestsQueue>;
+
+  void ProcessQueuedAccessRequest(const RequestsQueue& queue,
+                                  content::WebContents* web_contents);
+
+  void OnPickerDialogResults(content::WebContents* web_contents,
+                             content::DesktopMediaID source);
+
+  void AddNotificationObserver();
+
+  // content::NotificationObserver implementation.
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override;
+
+  void DeletePendingAccessRequest(int render_process_id,
+                                  int render_frame_id,
+                                  int page_request_id);
+
+  bool display_notification_ = true;
+  std::unique_ptr<DesktopMediaPickerFactory> picker_factory_;
+  RequestsQueues pending_requests_;
+  content::NotificationRegistrar notifications_registrar_;
+
+  DISALLOW_COPY_AND_ASSIGN(DisplayMediaAccessHandler);
+};
+
+#endif  // CHROME_BROWSER_MEDIA_WEBRTC_DISPLAY_MEDIA_ACCESS_HANDLER_H_
diff --git a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
new file mode 100644
index 0000000..79fb3e0
--- /dev/null
+++ b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/desktop_media_id.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/media_stream_request.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class DisplayMediaAccessHandlerTest : public ChromeRenderViewHostTestHarness {
+ public:
+  DisplayMediaAccessHandlerTest() {}
+  ~DisplayMediaAccessHandlerTest() override {}
+
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
+    picker_factory_ = picker_factory.get();
+    access_handler_ = std::make_unique<DisplayMediaAccessHandler>(
+        std::move(picker_factory), false /* display_notification */);
+  }
+
+  void ProcessRequest(
+      const content::DesktopMediaID& fake_desktop_media_id_response,
+      content::MediaStreamRequestResult* request_result,
+      content::MediaStreamDevices* devices_result) {
+    FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+        {true /* expect_screens */, true /* expect_windows*/,
+         true /* expect_tabs */, false /* expect_audio */,
+         fake_desktop_media_id_response /* selected_source */}};
+    picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+    content::MediaStreamRequest request(
+        0, 0, 0, GURL("http://origin/"), false, content::MEDIA_GENERATE_STREAM,
+        std::string(), std::string(), content::MEDIA_NO_SERVICE,
+        content::MEDIA_DISPLAY_VIDEO_CAPTURE, false);
+
+    base::RunLoop wait_loop;
+    content::MediaResponseCallback callback = base::BindOnce(
+        [](base::RunLoop* wait_loop,
+           content::MediaStreamRequestResult* request_result,
+           content::MediaStreamDevices* devices_result,
+           const content::MediaStreamDevices& devices,
+           content::MediaStreamRequestResult result,
+           std::unique_ptr<content::MediaStreamUI> ui) {
+          *request_result = result;
+          *devices_result = devices;
+          wait_loop->Quit();
+        },
+        &wait_loop, request_result, devices_result);
+    access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+                                   nullptr /* extension */);
+    wait_loop.Run();
+    EXPECT_TRUE(test_flags[0].picker_created);
+
+    access_handler_.reset();
+    EXPECT_TRUE(test_flags[0].picker_deleted);
+  }
+
+  void NotifyWebContentsDestroyed() {
+    access_handler_->Observe(
+        content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
+        content::Source<content::WebContents>(web_contents()),
+        content::NotificationDetails());
+  }
+
+  const DisplayMediaAccessHandler::RequestsQueues& GetRequestQueues() {
+    return access_handler_->pending_requests_;
+  }
+
+ protected:
+  FakeDesktopMediaPickerFactory* picker_factory_;
+  std::unique_ptr<DisplayMediaAccessHandler> access_handler_;
+};
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionGiven) {
+  content::MediaStreamRequestResult result;
+  content::MediaStreamDevices devices;
+  ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
+                                         content::DesktopMediaID::kFakeId),
+                 &result, &devices);
+  EXPECT_EQ(content::MEDIA_DEVICE_OK, result);
+  EXPECT_EQ(1u, devices.size());
+  EXPECT_EQ(content::MEDIA_DISPLAY_VIDEO_CAPTURE, devices[0].type);
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, PermissionDenied) {
+  content::MediaStreamRequestResult result;
+  content::MediaStreamDevices devices;
+  ProcessRequest(content::DesktopMediaID(), &result, &devices);
+  EXPECT_EQ(content::MEDIA_DEVICE_PERMISSION_DENIED, result);
+  EXPECT_EQ(0u, devices.size());
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, UpdateMediaRequestStateWithClosing) {
+  const int render_process_id = 0;
+  const int render_frame_id = 0;
+  const int page_request_id = 0;
+  const content::MediaStreamType stream_type =
+      content::MEDIA_DISPLAY_VIDEO_CAPTURE;
+  FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+      {true /* expect_screens */, true /* expect_windows*/,
+       true /* expect_tabs */, false /* expect_audio */,
+       content::DesktopMediaID(), true /* cancelled */}};
+  picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+  content::MediaStreamRequest request(
+      render_process_id, render_frame_id, page_request_id,
+      GURL("http://origin/"), false, content::MEDIA_GENERATE_STREAM,
+      std::string(), std::string(), content::MEDIA_NO_SERVICE, stream_type,
+      false);
+  content::MediaResponseCallback callback;
+  access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+                                 nullptr /* extension */);
+  EXPECT_TRUE(test_flags[0].picker_created);
+  EXPECT_EQ(1u, GetRequestQueues().size());
+  auto queue_it = GetRequestQueues().find(web_contents());
+  EXPECT_TRUE(queue_it != GetRequestQueues().end());
+  EXPECT_EQ(1u, queue_it->second.size());
+
+  access_handler_->UpdateMediaRequestState(
+      render_process_id, render_frame_id, page_request_id, stream_type,
+      content::MEDIA_REQUEST_STATE_CLOSING);
+  EXPECT_EQ(1u, GetRequestQueues().size());
+  queue_it = GetRequestQueues().find(web_contents());
+  EXPECT_TRUE(queue_it != GetRequestQueues().end());
+  EXPECT_EQ(0u, queue_it->second.size());
+  EXPECT_TRUE(test_flags[0].picker_deleted);
+  access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, WebContentsDestroyed) {
+  FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+      {true /* expect_screens */, true /* expect_windows*/,
+       true /* expect_tabs */, false /* expect_audio */,
+       content::DesktopMediaID(), true /* cancelled */}};
+  picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
+  content::MediaStreamRequest request(
+      0, 0, 0, GURL("http://origin/"), false, content::MEDIA_GENERATE_STREAM,
+      std::string(), std::string(), content::MEDIA_NO_SERVICE,
+      content::MEDIA_DISPLAY_VIDEO_CAPTURE, false);
+  content::MediaResponseCallback callback;
+  access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+                                 nullptr /* extension */);
+  EXPECT_TRUE(test_flags[0].picker_created);
+  EXPECT_EQ(1u, GetRequestQueues().size());
+  auto queue_it = GetRequestQueues().find(web_contents());
+  EXPECT_TRUE(queue_it != GetRequestQueues().end());
+  EXPECT_EQ(1u, queue_it->second.size());
+
+  NotifyWebContentsDestroyed();
+  EXPECT_EQ(0u, GetRequestQueues().size());
+  access_handler_.reset();
+}
+
+TEST_F(DisplayMediaAccessHandlerTest, MultipleRequests) {
+  FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
+      {true /* expect_screens */, true /* expect_windows*/,
+       true /* expect_tabs */, false /* expect_audio */,
+       content::DesktopMediaID(
+           content::DesktopMediaID::TYPE_SCREEN,
+           content::DesktopMediaID::kFakeId) /* selected_source */},
+      {true /* expect_screens */, true /* expect_windows*/,
+       true /* expect_tabs */, false /* expect_audio */,
+       content::DesktopMediaID(
+           content::DesktopMediaID::TYPE_WINDOW,
+           content::DesktopMediaID::kNullId) /* selected_source */}};
+  const size_t kTestFlagCount = 2;
+  picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
+
+  content::MediaStreamRequestResult result;
+  content::MediaStreamDevices devices;
+  base::RunLoop wait_loop[kTestFlagCount];
+  for (size_t i = 0; i < kTestFlagCount; ++i) {
+    content::MediaStreamRequest request(
+        0, 0, 0, GURL("http://origin/"), false, content::MEDIA_GENERATE_STREAM,
+        std::string(), std::string(), content::MEDIA_NO_SERVICE,
+        content::MEDIA_DISPLAY_VIDEO_CAPTURE, false);
+    content::MediaResponseCallback callback = base::BindOnce(
+        [](base::RunLoop* wait_loop,
+           content::MediaStreamRequestResult* request_result,
+           content::MediaStreamDevices* devices_result,
+           const content::MediaStreamDevices& devices,
+           content::MediaStreamRequestResult result,
+           std::unique_ptr<content::MediaStreamUI> ui) {
+          *request_result = result;
+          *devices_result = devices;
+          wait_loop->Quit();
+        },
+        &wait_loop[i], &result, &devices);
+    access_handler_->HandleRequest(web_contents(), request, std::move(callback),
+                                   nullptr /* extension */);
+  }
+  wait_loop[0].Run();
+  EXPECT_TRUE(test_flags[0].picker_created);
+  EXPECT_TRUE(test_flags[0].picker_deleted);
+  EXPECT_EQ(content::MEDIA_DEVICE_OK, result);
+  EXPECT_EQ(1u, devices.size());
+  EXPECT_EQ(content::MEDIA_DISPLAY_VIDEO_CAPTURE, devices[0].type);
+
+  content::MediaStreamDevice first_device = devices[0];
+  EXPECT_TRUE(test_flags[1].picker_created);
+  EXPECT_FALSE(test_flags[1].picker_deleted);
+  wait_loop[1].Run();
+  EXPECT_TRUE(test_flags[1].picker_deleted);
+  EXPECT_EQ(content::MEDIA_DEVICE_OK, result);
+  EXPECT_EQ(1u, devices.size());
+  EXPECT_EQ(content::MEDIA_DISPLAY_VIDEO_CAPTURE, devices[0].type);
+  EXPECT_FALSE(devices[0].IsSameDevice(first_device));
+
+  access_handler_.reset();
+}
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
index 52f5d57..69024fa 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/metrics/field_trial.h"
@@ -33,6 +36,10 @@
 #include "extensions/common/constants.h"
 #include "media/base/media_switches.h"
 
+#if !defined(OS_ANDROID)
+#include "chrome/browser/media/webrtc/display_media_access_handler.h"
+#endif  //  defined(OS_ANDROID)
+
 #if defined(OS_CHROMEOS)
 #include "ash/shell.h"
 #include "chrome/browser/media/chromeos_login_media_access_handler.h"
@@ -47,7 +54,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
-#endif
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 using content::BrowserThread;
 using content::MediaCaptureDevices;
@@ -76,12 +83,6 @@
   return web_contents;
 }
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-inline CaptureAccessHandlerBase* ToCaptureAccessHandlerBase(
-    MediaAccessHandler* handler) {
-  return static_cast<CaptureAccessHandlerBase*>(handler);
-}
-#endif
 }  // namespace
 
 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
@@ -93,6 +94,11 @@
       media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+#if !defined(OS_ANDROID)
+  media_access_handlers_.push_back(
+      std::make_unique<DisplayMediaAccessHandler>());
+#endif  //  defined(OS_ANDROID)
+
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #if defined(OS_CHROMEOS)
   media_access_handlers_.push_back(
@@ -338,9 +344,8 @@
           page_request_id, security_origin, stream_type, state));
 }
 
-void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
-    int render_process_id,
-    int render_frame_id) {
+void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(int render_process_id,
+                                                          int render_frame_id) {
   // TODO(https://crbug.com/837606): Figure out how to simplify threading here.
   // Currently, this will either always be called on the UI thread, or always
   // on the IO thread, depending on how far along the work to migrate to the
@@ -419,21 +424,12 @@
     int render_process_id,
     int render_frame_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-#if BUILDFLAG(ENABLE_EXTENSIONS)
+
   for (const auto& handler : media_access_handlers_) {
-    if (handler->SupportsStreamType(
-            WebContentsFromIds(render_process_id, render_frame_id),
-            content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, nullptr) ||
-        handler->SupportsStreamType(
-            WebContentsFromIds(render_process_id, render_frame_id),
-            content::MEDIA_GUM_TAB_VIDEO_CAPTURE, nullptr)) {
-      if (ToCaptureAccessHandlerBase(handler.get())
-              ->IsInsecureCapturingInProgress(render_process_id,
-                                              render_frame_id))
-        return true;
-    }
+    if (handler->IsInsecureCapturingInProgress(render_process_id,
+                                               render_frame_id))
+      return true;
   }
-#endif
   return false;
 }
 
@@ -455,9 +451,10 @@
     bool is_secure) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
-      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
+      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE &&
+      stream_type != content::MEDIA_DISPLAY_VIDEO_CAPTURE) {
     return;
-
+  }
   BrowserThread::PostTask(
       BrowserThread::UI, FROM_HERE,
       base::BindOnce(&MediaCaptureDevicesDispatcher::UpdateCapturingLinkSecured,
@@ -473,19 +470,14 @@
     bool is_secure) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (stream_type != content::MEDIA_GUM_TAB_VIDEO_CAPTURE &&
-      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE)
+      stream_type != content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE &&
+      stream_type != content::MEDIA_DISPLAY_VIDEO_CAPTURE) {
     return;
-
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  for (const auto& handler : media_access_handlers_) {
-    if (handler->SupportsStreamType(
-            WebContentsFromIds(render_process_id, render_frame_id), stream_type,
-            nullptr)) {
-      ToCaptureAccessHandlerBase(handler.get())
-          ->UpdateCapturingLinkSecured(render_process_id, render_frame_id,
-                                       page_request_id, is_secure);
-      break;
-    }
   }
-#endif
+
+  for (const auto& handler : media_access_handlers_) {
+    handler->UpdateCapturingLinkSecured(render_process_id, render_frame_id,
+                                        page_request_id, is_secure);
+    break;
+  }
 }
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 3595a40..ac2a7837 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/metrics/field_trial.h"
@@ -28,6 +29,7 @@
 #include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
 #include "chrome/browser/permissions/permission_uma_util.h"
 #include "chrome/browser/permissions/permission_util.h"
+
 #endif  // defined(OS_ANDROID)
 
 using content::BrowserThread;
@@ -69,7 +71,8 @@
 #if defined(OS_ANDROID)
   return type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
          type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
-         type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE;
+         type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE ||
+         type == content::MEDIA_DISPLAY_VIDEO_CAPTURE;
 #else
   return type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
          type == content::MEDIA_DEVICE_AUDIO_CAPTURE;
@@ -107,7 +110,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
 #if defined(OS_ANDROID)
-  if (request.video_type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE &&
+  if (IsScreenCaptureMediaType(request.video_type) &&
       !base::FeatureList::IsEnabled(
           chrome::android::kUserMediaScreenCapturing)) {
     // If screen capturing isn't enabled on Android, we'll use "invalid state"
@@ -143,7 +146,7 @@
 
   const content::MediaStreamRequest request = it->second.front().request;
 #if defined(OS_ANDROID)
-  if (request.video_type == content::MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) {
+  if (IsScreenCaptureMediaType(request.video_type)) {
     ScreenCaptureInfoBarDelegateAndroid::Create(
         web_contents, request,
         base::Bind(&PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5edc626..e9539bc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3018,6 +3018,7 @@
       "../browser/media/router/discovery/discovery_network_monitor_metric_observer_unittest.cc",
       "../browser/media/router/discovery/discovery_network_monitor_unittest.cc",
       "../browser/media/unified_autoplay_config_unittest.cc",
+      "../browser/media/webrtc/display_media_access_handler_unittest.cc",
       "../browser/media/webrtc/tab_desktop_media_list_unittest.cc",
       "../browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc",
       "../browser/media/webrtc/webrtc_event_log_manager_unittest.cc",
diff --git a/content/public/common/media_stream_request.cc b/content/public/common/media_stream_request.cc
index 532cccd..3bcbcc9 100644
--- a/content/public/common/media_stream_request.cc
+++ b/content/public/common/media_stream_request.cc
@@ -22,7 +22,8 @@
 }
 
 bool IsScreenCaptureMediaType(MediaStreamType type) {
-  return (type == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
+  return (type == MEDIA_DISPLAY_VIDEO_CAPTURE ||
+          type == MEDIA_GUM_TAB_AUDIO_CAPTURE ||
           type == MEDIA_GUM_TAB_VIDEO_CAPTURE ||
           type == MEDIA_GUM_DESKTOP_AUDIO_CAPTURE ||
           type == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE);