diff --git a/BUILD.gn b/BUILD.gn
index 59a9f7d5f..2d418501b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -591,6 +591,8 @@ static_library("libcef_static") {
     "libcef/browser/media_router/media_sink_impl.h",
     "libcef/browser/media_router/media_source_impl.cc",
     "libcef/browser/media_router/media_source_impl.h",
+    "libcef/browser/media_stream_registrar.cc",
+    "libcef/browser/media_stream_registrar.h",
     "libcef/browser/menu_manager.cc",
     "libcef/browser/menu_manager.h",
     "libcef/browser/menu_model_impl.cc",
diff --git a/include/capi/cef_client_capi.h b/include/capi/cef_client_capi.h
index ece0f24d6..6bd4837d6 100644
--- a/include/capi/cef_client_capi.h
+++ b/include/capi/cef_client_capi.h
@@ -33,7 +33,7 @@
 // by hand. See the translator.README.txt file in the tools directory for
 // more information.
 //
-// $hash=7e03d64dfcefc287c083e35e5ef9b3fa4f762b1b$
+// $hash=03ae4ba9762510e2b0c19ea29322c20ebaf2e683$
 //
 
 #ifndef CEF_INCLUDE_CAPI_CEF_CLIENT_CAPI_H_
@@ -141,8 +141,7 @@ typedef struct _cef_client_t {
       struct _cef_client_t* self);
 
   ///
-  // Return the handler for permission requests. If no handler is provided
-  // requests be denied by default.
+  // Return the handler for permission requests.
   ///
   struct _cef_permission_handler_t*(CEF_CALLBACK* get_permission_handler)(
       struct _cef_client_t* self);
diff --git a/include/capi/cef_display_handler_capi.h b/include/capi/cef_display_handler_capi.h
index efc0a19e9..7b317ab3b 100644
--- a/include/capi/cef_display_handler_capi.h
+++ b/include/capi/cef_display_handler_capi.h
@@ -33,7 +33,7 @@
 // by hand. See the translator.README.txt file in the tools directory for
 // more information.
 //
-// $hash=142637539a094a03adc71d2f3f5b711ba64918b1$
+// $hash=5e52ae520b7eda3595683d428aa578bbc776956b$
 //
 
 #ifndef CEF_INCLUDE_CAPI_CEF_DISPLAY_HANDLER_CAPI_H_
@@ -154,6 +154,16 @@ typedef struct _cef_display_handler_t {
       cef_cursor_handle_t cursor,
       cef_cursor_type_t type,
       const struct _cef_cursor_info_t* custom_cursor_info);
+
+  ///
+  // Called when the browser's access to an audio and/or video source has
+  // changed.
+  ///
+  void(CEF_CALLBACK* on_media_access_change)(
+      struct _cef_display_handler_t* self,
+      struct _cef_browser_t* browser,
+      int has_video_access,
+      int has_audio_access);
 } cef_display_handler_t;
 
 #ifdef __cplusplus
diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h
index b9228316c..73eb2071e 100644
--- a/include/cef_api_hash.h
+++ b/include/cef_api_hash.h
@@ -42,13 +42,13 @@
 // way that may cause binary incompatibility with other builds. The universal
 // hash value will change if any platform is affected whereas the platform hash
 // values will change only if that particular platform is affected.
-#define CEF_API_HASH_UNIVERSAL "f0b6806a3e15f849013e58992ef11c99e5cfeb60"
+#define CEF_API_HASH_UNIVERSAL "794a4cf2ad83db17558bd2ca2d721487875a37e8"
 #if defined(OS_WIN)
-#define CEF_API_HASH_PLATFORM "55ba6603a77fcbf93d58a44fbeae9ea52d000153"
+#define CEF_API_HASH_PLATFORM "aa627f71c1cbdf13beeb3fe740337f4cc1cb5dc5"
 #elif defined(OS_MAC)
-#define CEF_API_HASH_PLATFORM "4028739577ee54f7049f354acd724ecee071aa22"
+#define CEF_API_HASH_PLATFORM "6bda80f2ee107a22780193a7af9101eb5a4db8a0"
 #elif defined(OS_LINUX)
-#define CEF_API_HASH_PLATFORM "7464b32d2ed43a15d68a651199fda0c0fe040a8d"
+#define CEF_API_HASH_PLATFORM "c83b942ce72835d0ea01bfc4cab3928b92abdb85"
 #endif
 
 #ifdef __cplusplus
diff --git a/include/cef_display_handler.h b/include/cef_display_handler.h
index 61636f104..0fd20aad3 100644
--- a/include/cef_display_handler.h
+++ b/include/cef_display_handler.h
@@ -148,6 +148,15 @@ class CefDisplayHandler : public virtual CefBaseRefCounted {
                               const CefCursorInfo& custom_cursor_info) {
     return false;
   }
+
+  ///
+  // Called when the browser's access to an audio and/or video source has
+  // changed.
+  ///
+  /*--cef()--*/
+  virtual void OnMediaAccessChange(CefRefPtr<CefBrowser> browser,
+                                   bool has_video_access,
+                                   bool has_audio_access) {}
 };
 
 #endif  // CEF_INCLUDE_CEF_DISPLAY_HANDLER_H_
diff --git a/libcef/browser/browser_host_base.cc b/libcef/browser/browser_host_base.cc
index c855fe8c4..41e68c338 100644
--- a/libcef/browser/browser_host_base.cc
+++ b/libcef/browser/browser_host_base.cc
@@ -204,9 +204,10 @@ void CefBrowserHostBase::InitializeBrowser() {
 void CefBrowserHostBase::DestroyBrowser() {
   CEF_REQUIRE_UIT();
 
-  devtools_manager_.reset(nullptr);
+  devtools_manager_.reset();
+  media_stream_registrar_.reset();
 
-  platform_delegate_.reset(nullptr);
+  platform_delegate_.reset();
 
   contents_delegate_->RemoveObserver(this);
   contents_delegate_->ObserveWebContents(nullptr);
@@ -972,6 +973,14 @@ content::BrowserContext* CefBrowserHostBase::GetBrowserContext() const {
   return nullptr;
 }
 
+CefMediaStreamRegistrar* CefBrowserHostBase::GetMediaStreamRegistrar() {
+  CEF_REQUIRE_UIT();
+  if (!media_stream_registrar_) {
+    media_stream_registrar_ = std::make_unique<CefMediaStreamRegistrar>(this);
+  }
+  return media_stream_registrar_.get();
+}
+
 views::Widget* CefBrowserHostBase::GetWindowWidget() const {
   CEF_REQUIRE_UIT();
   if (!platform_delegate_)
diff --git a/libcef/browser/browser_host_base.h b/libcef/browser/browser_host_base.h
index d1bd9aee7..58778891f 100644
--- a/libcef/browser/browser_host_base.h
+++ b/libcef/browser/browser_host_base.h
@@ -15,6 +15,7 @@
 #include "libcef/browser/devtools/devtools_manager.h"
 #include "libcef/browser/file_dialog_manager.h"
 #include "libcef/browser/frame_host_impl.h"
+#include "libcef/browser/media_stream_registrar.h"
 #include "libcef/browser/request_context_impl.h"
 
 #include "base/observer_list.h"
@@ -301,6 +302,7 @@ class CefBrowserHostBase : public CefBrowserHost,
   CefBrowserContentsDelegate* contents_delegate() const {
     return contents_delegate_.get();
   }
+  CefMediaStreamRegistrar* GetMediaStreamRegistrar();
 
   // Returns the Widget owner for the browser window. Only used with windowed
   // browsers.
@@ -371,6 +373,8 @@ class CefBrowserHostBase : public CefBrowserHost,
   // Used for creating and managing DevTools instances.
   std::unique_ptr<CefDevToolsManager> devtools_manager_;
 
+  std::unique_ptr<CefMediaStreamRegistrar> media_stream_registrar_;
+
  private:
   IMPLEMENT_REFCOUNTING(CefBrowserHostBase);
 };
diff --git a/libcef/browser/media_access_query.cc b/libcef/browser/media_access_query.cc
index 73a0a83e9..d9e92a00f 100644
--- a/libcef/browser/media_access_query.cc
+++ b/libcef/browser/media_access_query.cc
@@ -7,6 +7,7 @@
 #include "include/cef_permission_handler.h"
 #include "libcef/browser/browser_host_base.h"
 #include "libcef/browser/media_capture_devices_dispatcher.h"
+#include "libcef/browser/media_stream_registrar.h"
 #include "libcef/common/cef_switches.h"
 
 #include "base/command_line.h"
@@ -20,13 +21,17 @@ class CefMediaAccessQuery {
  public:
   using CallbackType = content::MediaResponseCallback;
 
-  explicit CefMediaAccessQuery(const content::MediaStreamRequest& request,
-                               CallbackType&& callback)
-      : request_(request), callback_(std::move(callback)) {}
+  CefMediaAccessQuery(CefBrowserHostBase* const browser,
+                      const content::MediaStreamRequest& request,
+                      CallbackType&& callback)
+      : browser_(browser), request_(request), callback_(std::move(callback)) {}
 
   CefMediaAccessQuery(CefMediaAccessQuery&& query)
-      : request_(query.request_), callback_(std::move(query.callback_)) {}
+      : browser_(query.browser_),
+        request_(query.request_),
+        callback_(std::move(query.callback_)) {}
   CefMediaAccessQuery& operator=(CefMediaAccessQuery&& query) {
+    browser_ = query.browser_;
     request_ = query.request_;
     callback_ = std::move(query.callback_);
     return *this;
@@ -74,8 +79,20 @@ class CefMediaAccessQuery {
                      : blink::mojom::MediaStreamRequestResult::OK;
     }
 
+    bool has_video = false;
+    bool has_audio = false;
+    if (!stream_devices_set->stream_devices.empty()) {
+      blink::mojom::StreamDevices& devices =
+          *stream_devices_set->stream_devices[0];
+      has_video = devices.video_device.has_value();
+      has_audio = devices.audio_device.has_value();
+    }
+    auto media_stream_ui =
+        browser_->GetMediaStreamRegistrar()->MaybeCreateMediaStreamUI(
+            has_video, has_audio);
+
     std::move(callback_).Run(*stream_devices_set, result,
-                             std::unique_ptr<content::MediaStreamUI>());
+                             std::move(media_stream_ui));
   }
 
  private:
@@ -205,6 +222,7 @@ class CefMediaAccessQuery {
     return stream_devices_set;
   }
 
+  CefRefPtr<CefBrowserHostBase> browser_;
   content::MediaStreamRequest request_;
   CallbackType callback_;
 };
@@ -281,7 +299,7 @@ void RequestMediaAccessPermission(CefBrowserHostBase* browser,
                                   content::MediaResponseCallback callback) {
   CEF_REQUIRE_UIT();
 
-  CefMediaAccessQuery query(request, std::move(callback));
+  CefMediaAccessQuery query(browser, request, std::move(callback));
 
   if (CheckCommandLinePermission()) {
     // Allow all requested permissions.
diff --git a/libcef/browser/media_stream_registrar.cc b/libcef/browser/media_stream_registrar.cc
new file mode 100644
index 000000000..821648d1f
--- /dev/null
+++ b/libcef/browser/media_stream_registrar.cc
@@ -0,0 +1,108 @@
+// Copyright 2022 The Chromium Embedded Framework 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 "libcef/browser/media_stream_registrar.h"
+
+#include "libcef/browser/browser_host_base.h"
+#include "libcef/browser/thread_util.h"
+
+class CefMediaStreamUI : public content::MediaStreamUI {
+ public:
+  CefMediaStreamUI(base::WeakPtr<CefMediaStreamRegistrar> registrar,
+                   bool has_video,
+                   bool has_audio)
+      : registrar_(registrar), has_video_(has_video), has_audio_(has_audio) {}
+
+  ~CefMediaStreamUI() override {
+    if (registrar_) {
+      registrar_->UnregisterMediaStream(label_);
+    }
+  }
+
+  CefMediaStreamUI(const CefMediaStreamUI&) = delete;
+  CefMediaStreamUI& operator=(const CefMediaStreamUI&) = delete;
+
+  gfx::NativeViewId OnStarted(
+      base::RepeatingClosure stop,
+      SourceCallback source,
+      const std::string& label,
+      std::vector<content::DesktopMediaID> screen_capture_ids,
+      StateChangeCallback state_change) override {
+    if (registrar_) {
+      label_ = label;
+      registrar_->RegisterMediaStream(label, has_video_, has_audio_);
+    }
+    return 0;
+  }
+
+  void OnDeviceStoppedForSourceChange(
+      const std::string& label,
+      const content::DesktopMediaID& old_media_id,
+      const content::DesktopMediaID& new_media_id) override {}
+
+  void OnDeviceStopped(const std::string& label,
+                       const content::DesktopMediaID& media_id) override {}
+
+ private:
+  base::WeakPtr<CefMediaStreamRegistrar> registrar_;
+  const bool has_video_;
+  const bool has_audio_;
+  std::string label_;
+};
+
+CefMediaStreamRegistrar::CefMediaStreamRegistrar(CefBrowserHostBase* browser)
+    : browser_(browser) {}
+
+std::unique_ptr<content::MediaStreamUI>
+CefMediaStreamRegistrar::MaybeCreateMediaStreamUI(bool has_video,
+                                                  bool has_audio) const {
+  // Only create the object if the callback will be executed.
+  if (auto client = browser_->GetClient()) {
+    if (auto handler = client->GetDisplayHandler()) {
+      return std::make_unique<CefMediaStreamUI>(weak_ptr_factory_.GetWeakPtr(),
+                                                has_video, has_audio);
+    }
+  }
+  return nullptr;
+}
+
+void CefMediaStreamRegistrar::RegisterMediaStream(const std::string& label,
+                                                  bool video,
+                                                  bool audio) {
+  CEF_REQUIRE_UIT();
+  MediaStreamInfo info = {video, audio};
+  registered_streams_.insert(std::make_pair(label, info));
+  NotifyMediaStreamChange();
+}
+
+void CefMediaStreamRegistrar::UnregisterMediaStream(const std::string& label) {
+  CEF_REQUIRE_UIT();
+  registered_streams_.erase(label);
+  NotifyMediaStreamChange();
+}
+
+void CefMediaStreamRegistrar::NotifyMediaStreamChange() {
+  bool video = false;
+  bool audio = false;
+  for (const auto& media_stream : registered_streams_) {
+    const auto& info = media_stream.second;
+    if (!video)
+      video = info.video;
+    if (!audio)
+      audio = info.audio;
+  }
+
+  if (audio == last_notified_info_.audio &&
+      video == last_notified_info_.video) {
+    return;
+  }
+
+  last_notified_info_ = {video, audio};
+
+  if (auto client = browser_->GetClient()) {
+    if (auto handler = client->GetDisplayHandler()) {
+      handler->OnMediaAccessChange(browser_, video, audio);
+    }
+  }
+}
diff --git a/libcef/browser/media_stream_registrar.h b/libcef/browser/media_stream_registrar.h
new file mode 100644
index 000000000..6a34c7f71
--- /dev/null
+++ b/libcef/browser/media_stream_registrar.h
@@ -0,0 +1,56 @@
+// Copyright 2022 The Chromium Embedded Framework 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 CEF_LIBCEF_BROWSER_MEDIA_STREAM_REGISTRAR_H_
+#define CEF_LIBCEF_BROWSER_MEDIA_STREAM_REGISTRAR_H_
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/media_stream_request.h"
+
+class CefBrowserHostBase;
+class CefMediaStreamUI;
+
+class CefMediaStreamRegistrar {
+ public:
+  explicit CefMediaStreamRegistrar(CefBrowserHostBase* browser);
+
+  CefMediaStreamRegistrar(const CefMediaStreamRegistrar&) = delete;
+  CefMediaStreamRegistrar& operator=(const CefMediaStreamRegistrar&) = delete;
+
+  std::unique_ptr<content::MediaStreamUI> MaybeCreateMediaStreamUI(
+      bool has_video,
+      bool has_audio) const;
+
+ private:
+  friend class CefMediaStreamUI;
+
+  // Called from CefMediaStreamUI.
+  void RegisterMediaStream(const std::string& label, bool video, bool audio);
+  void UnregisterMediaStream(const std::string& label);
+
+  void NotifyMediaStreamChange();
+
+  // Guaranteed to outlive this object.
+  CefBrowserHostBase* const browser_;
+
+  struct MediaStreamInfo {
+    bool video;
+    bool audio;
+  };
+
+  // Current in use media streams.
+  std::map<std::string, MediaStreamInfo> registered_streams_;
+
+  // Last notified media stream info.
+  MediaStreamInfo last_notified_info_{};
+
+  base::WeakPtrFactory<CefMediaStreamRegistrar> weak_ptr_factory_{this};
+};
+
+#endif  // CEF_LIBCEF_BROWSER_MEDIA_STREAM_REGISTRAR_H_
diff --git a/libcef_dll/cpptoc/display_handler_cpptoc.cc b/libcef_dll/cpptoc/display_handler_cpptoc.cc
index d5720b73b..a0a14a125 100644
--- a/libcef_dll/cpptoc/display_handler_cpptoc.cc
+++ b/libcef_dll/cpptoc/display_handler_cpptoc.cc
@@ -9,7 +9,7 @@
 // implementations. See the translator.README.txt file in the tools directory
 // for more information.
 //
-// $hash=5bcef102e9ae42a32b551c3af3decbae11b8b37d$
+// $hash=b492dccf2a5ddb50f50fe1783d8cacd3080714a5$
 //
 
 #include "libcef_dll/cpptoc/display_handler_cpptoc.h"
@@ -292,6 +292,29 @@ int CEF_CALLBACK display_handler_on_cursor_change(
   return _retval;
 }
 
+void CEF_CALLBACK
+display_handler_on_media_access_change(struct _cef_display_handler_t* self,
+                                       cef_browser_t* browser,
+                                       int has_video_access,
+                                       int has_audio_access) {
+  shutdown_checker::AssertNotShutdown();
+
+  // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
+
+  DCHECK(self);
+  if (!self)
+    return;
+  // Verify param: browser; type: refptr_diff
+  DCHECK(browser);
+  if (!browser)
+    return;
+
+  // Execute
+  CefDisplayHandlerCppToC::Get(self)->OnMediaAccessChange(
+      CefBrowserCToCpp::Wrap(browser), has_video_access ? true : false,
+      has_audio_access ? true : false);
+}
+
 }  // namespace
 
 // CONSTRUCTOR - Do not edit by hand.
@@ -309,6 +332,7 @@ CefDisplayHandlerCppToC::CefDisplayHandlerCppToC() {
   GetStruct()->on_loading_progress_change =
       display_handler_on_loading_progress_change;
   GetStruct()->on_cursor_change = display_handler_on_cursor_change;
+  GetStruct()->on_media_access_change = display_handler_on_media_access_change;
 }
 
 // DESTRUCTOR - Do not edit by hand.
diff --git a/libcef_dll/ctocpp/display_handler_ctocpp.cc b/libcef_dll/ctocpp/display_handler_ctocpp.cc
index bd0d81d7f..2805c7d75 100644
--- a/libcef_dll/ctocpp/display_handler_ctocpp.cc
+++ b/libcef_dll/ctocpp/display_handler_ctocpp.cc
@@ -9,7 +9,7 @@
 // implementations. See the translator.README.txt file in the tools directory
 // for more information.
 //
-// $hash=f40564d59c337fede5e8c3121ed735166d5e05a3$
+// $hash=ab688c32624070704507d10dc27d430d90f04dd2$
 //
 
 #include "libcef_dll/ctocpp/display_handler_ctocpp.h"
@@ -277,6 +277,28 @@ bool CefDisplayHandlerCToCpp::OnCursorChange(
   return _retval ? true : false;
 }
 
+NO_SANITIZE("cfi-icall")
+void CefDisplayHandlerCToCpp::OnMediaAccessChange(CefRefPtr<CefBrowser> browser,
+                                                  bool has_video_access,
+                                                  bool has_audio_access) {
+  shutdown_checker::AssertNotShutdown();
+
+  cef_display_handler_t* _struct = GetStruct();
+  if (CEF_MEMBER_MISSING(_struct, on_media_access_change))
+    return;
+
+  // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
+
+  // Verify param: browser; type: refptr_diff
+  DCHECK(browser.get());
+  if (!browser.get())
+    return;
+
+  // Execute
+  _struct->on_media_access_change(_struct, CefBrowserCppToC::Wrap(browser),
+                                  has_video_access, has_audio_access);
+}
+
 // CONSTRUCTOR - Do not edit by hand.
 
 CefDisplayHandlerCToCpp::CefDisplayHandlerCToCpp() {}
diff --git a/libcef_dll/ctocpp/display_handler_ctocpp.h b/libcef_dll/ctocpp/display_handler_ctocpp.h
index 61e98b430..7d1b28922 100644
--- a/libcef_dll/ctocpp/display_handler_ctocpp.h
+++ b/libcef_dll/ctocpp/display_handler_ctocpp.h
@@ -9,7 +9,7 @@
 // implementations. See the translator.README.txt file in the tools directory
 // for more information.
 //
-// $hash=f6769db4bda5143d4e42a2e68788b58c68292c1d$
+// $hash=ca2ef5029377f07f196011a3ec686049d3623cce$
 //
 
 #ifndef CEF_LIBCEF_DLL_CTOCPP_DISPLAY_HANDLER_CTOCPP_H_
@@ -61,6 +61,9 @@ class CefDisplayHandlerCToCpp
                       CefCursorHandle cursor,
                       cef_cursor_type_t type,
                       const CefCursorInfo& custom_cursor_info) override;
+  void OnMediaAccessChange(CefRefPtr<CefBrowser> browser,
+                           bool has_video_access,
+                           bool has_audio_access) override;
 };
 
 #endif  // CEF_LIBCEF_DLL_CTOCPP_DISPLAY_HANDLER_CTOCPP_H_
diff --git a/tests/ceftests/media_access_unittest.cc b/tests/ceftests/media_access_unittest.cc
index c2079be2a..4978934a1 100644
--- a/tests/ceftests/media_access_unittest.cc
+++ b/tests/ceftests/media_access_unittest.cc
@@ -49,6 +49,7 @@ class TestSetup {
   TrackCallback got_success;
   TrackCallback got_audio;
   TrackCallback got_video;
+  TrackCallback got_change;
 };
 
 class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler {
@@ -105,23 +106,14 @@ class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler {
         "exit?result=${val}&data=${encodeURIComponent(JSON.stringify(data))}`;"
         "}";
 
-    const bool want_audio_device =
-        request_ & CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE;
-    const bool want_video_device =
-        request_ & CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE;
-    const bool want_desktop_audio =
-        request_ & CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE;
-    const bool want_desktop_video =
-        request_ & CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE;
-
-    if (want_audio_device || want_video_device) {
+    if (want_audio_device() || want_video_device()) {
       page += std::string("navigator.mediaDevices.getUserMedia({audio: ") +
-              (want_audio_device ? "true" : "false") +
-              ", video: " + (want_video_device ? "true" : "false") + "})";
+              (want_audio_device() ? "true" : "false") +
+              ", video: " + (want_video_device() ? "true" : "false") + "})";
     } else {
       page += std::string("navigator.mediaDevices.getDisplayMedia({audio: ") +
-              (want_desktop_audio ? "true" : "false") +
-              ", video: " + (want_desktop_video ? "true" : "false") + "})";
+              (want_audio_desktop() ? "true" : "false") +
+              ", video: " + (want_video_desktop() ? "true" : "false") + "})";
     }
 
     page +=
@@ -188,7 +180,43 @@ class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler {
     return true;
   }
 
- protected:
+  void OnMediaAccessChange(CefRefPtr<CefBrowser> browser,
+                           bool has_video_access,
+                           bool has_audio_access) override {
+    EXPECT_UI_THREAD();
+    EXPECT_EQ(got_video_device() || got_video_desktop(), has_video_access);
+    EXPECT_EQ(got_audio_device() || got_audio_desktop(), has_audio_access);
+    EXPECT_FALSE(test_setup_->got_change);
+    test_setup_->got_change.yes();
+  }
+
+ private:
+  bool want_audio_device() const {
+    return request_ & CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE;
+  }
+  bool want_video_device() const {
+    return request_ & CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE;
+  }
+  bool want_audio_desktop() const {
+    return request_ & CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE;
+  }
+  bool want_video_desktop() const {
+    return request_ & CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE;
+  }
+
+  bool got_audio_device() const {
+    return response_ & CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE;
+  }
+  bool got_video_device() const {
+    return response_ & CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE;
+  }
+  bool got_audio_desktop() const {
+    return response_ & CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE;
+  }
+  bool got_video_desktop() const {
+    return response_ & CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE;
+  }
+
   TestSetup* const test_setup_;
   const uint32 request_;
   const uint32 response_;
@@ -213,6 +241,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningFalse) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermission) {
@@ -229,6 +258,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermission) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermissionAsync) {
@@ -246,6 +276,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermissionAsync) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenRequestingAudioButReturningVideo) {
@@ -260,6 +291,7 @@ TEST(MediaAccessTest, DeviceFailureWhenRequestingAudioButReturningVideo) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenRequestingVideoButReturningAudio) {
@@ -274,6 +306,7 @@ TEST(MediaAccessTest, DeviceFailureWhenRequestingVideoButReturningAudio) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DevicePartialFailureReturningVideo) {
@@ -290,6 +323,7 @@ TEST(MediaAccessTest, DevicePartialFailureReturningVideo) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DevicePartialFailureReturningAudio) {
@@ -306,6 +340,7 @@ TEST(MediaAccessTest, DevicePartialFailureReturningAudio) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture1) {
@@ -322,6 +357,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture1) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture2) {
@@ -338,6 +374,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture2) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture3) {
@@ -352,6 +389,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture3) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture4) {
@@ -366,6 +404,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture4) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture5) {
@@ -380,6 +419,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture5) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture6) {
@@ -394,6 +434,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture6) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceSuccessAudioOnly) {
@@ -408,6 +449,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioOnly) {
   EXPECT_TRUE(test_setup.got_success);
   EXPECT_TRUE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_TRUE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceSuccessVideoOnly) {
@@ -422,6 +464,7 @@ TEST(MediaAccessTest, DeviceSuccessVideoOnly) {
   EXPECT_TRUE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_TRUE(test_setup.got_video);
+  EXPECT_TRUE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceSuccessAudioVideo) {
@@ -439,6 +482,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioVideo) {
   EXPECT_TRUE(test_setup.got_success);
   EXPECT_TRUE(test_setup.got_audio);
   EXPECT_TRUE(test_setup.got_video);
+  EXPECT_TRUE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DeviceSuccessAudioVideoAsync) {
@@ -457,6 +501,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioVideoAsync) {
   EXPECT_TRUE(test_setup.got_success);
   EXPECT_TRUE(test_setup.got_audio);
   EXPECT_TRUE(test_setup.got_video);
+  EXPECT_TRUE(test_setup.got_change);
 }
 
 // Screen capture tests
@@ -474,6 +519,7 @@ TEST(MediaAccessTest, DesktopFailureWhenReturningNoPermission) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DesktopFailureWhenRequestingVideoButReturningAudio) {
@@ -488,6 +534,7 @@ TEST(MediaAccessTest, DesktopFailureWhenRequestingVideoButReturningAudio) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DesktopPartialSuccessReturningVideo) {
@@ -504,6 +551,7 @@ TEST(MediaAccessTest, DesktopPartialSuccessReturningVideo) {
   EXPECT_TRUE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_TRUE(test_setup.got_video);
+  EXPECT_TRUE(test_setup.got_change);
 }
 
 TEST(MediaAccessTest, DesktopPartialFailureReturningAudio) {
@@ -519,6 +567,7 @@ TEST(MediaAccessTest, DesktopPartialFailureReturningAudio) {
   EXPECT_FALSE(test_setup.got_success);
   EXPECT_FALSE(test_setup.got_audio);
   EXPECT_FALSE(test_setup.got_video);
+  EXPECT_FALSE(test_setup.got_change);
 }
 
 // Entry point for creating media access browser test objects.