[go: nahoru, domu]

Limit the number of `PermissionObserver` handles open by the Permissions API

This CL ensures that only at most one handle is open per (permission type, execution handle) pair.
It introduces a new class PermissionStatusListener that will hold the actual handle
and the PermissionStatus objects will hold a reference to this class.

This means that the PermissionType enum needs to be accessible in blink
and as such it's been moved from
`content/public/browser/permission_type.h`
to
`third_party/blink/public/common/permissions/permission_utils.h`

Additionally fix bugs uncovered along the way:
* If a PTZ (pan-tilt-zoom capable camera) request is made but it results
in a simple camera one, the Permissions API still tracks the PTZ
permission which would be incorrect.
* The New Tab Page doesn't properly get permission updates on the
PermissionStatus handle because of its weird URL. (`chrome://newtab`)

Fixed: 1122423
AX-Relnotes: n/a

Change-Id: Ieb5e9c7203f7bf3101a41af718a7d211b8bb40f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3359620
Reviewed-by: Ravjit Uppal <ravjit@chromium.org>
Reviewed-by: Kamila Hasanbega <hkamila@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Commit-Queue: Andy Paicu <andypaicu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#995106}
diff --git a/components/permissions/permission_manager.cc b/components/permissions/permission_manager.cc
index f4ca147..3aef9c1 100644
--- a/components/permissions/permission_manager.cc
+++ b/components/permissions/permission_manager.cc
@@ -704,6 +704,7 @@
     content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
         subscription->render_process_id, subscription->render_frame_id);
     GURL embedding_origin;
+    GURL requesting_origin = subscription->requesting_origin;
     if (rfh) {
       embedding_origin =
           GetEmbeddingOrigin(rfh, subscription->requesting_origin);
@@ -711,7 +712,7 @@
       embedding_origin = subscription->requesting_origin;
     }
 
-    if (!primary_pattern.Matches(subscription->requesting_origin) ||
+    if (!primary_pattern.Matches(requesting_origin) ||
         !secondary_pattern.Matches(embedding_origin)) {
       continue;
     }
diff --git a/components/permissions/permission_util.cc b/components/permissions/permission_util.cc
index 1ea019c..f0446a50 100644
--- a/components/permissions/permission_util.cc
+++ b/components/permissions/permission_util.cc
@@ -10,7 +10,6 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/permissions/features.h"
-#include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
diff --git a/components/permissions/permission_util.h b/components/permissions/permission_util.h
index b3a8f816..a55864a 100644
--- a/components/permissions/permission_util.h
+++ b/components/permissions/permission_util.h
@@ -11,9 +11,9 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/permissions/permission_request.h"
+#include "content/public/browser/permission_type.h"
 
 namespace content {
-enum class PermissionType;
 class RenderFrameHost;
 }  // namespace content
 
diff --git a/content/browser/permissions/permission_service_context.cc b/content/browser/permissions/permission_service_context.cc
index c75a14c..acaa026 100644
--- a/content/browser/permissions/permission_service_context.cc
+++ b/content/browser/permissions/permission_service_context.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -104,9 +105,11 @@
 void PermissionServiceContext::CreateService(
     mojo::PendingReceiver<blink::mojom::PermissionService> receiver) {
   DCHECK(render_frame_host_);
-  services_.Add(std::make_unique<PermissionServiceImpl>(
-                    this, render_frame_host_->GetLastCommittedOrigin()),
-                std::move(receiver));
+  services_.Add(
+      std::make_unique<PermissionServiceImpl>(
+          this, url::Origin::Create(PermissionUtil::GetLastCommittedOriginAsURL(
+                    render_frame_host_))),
+      std::move(receiver));
 }
 
 void PermissionServiceContext::CreateServiceForWorker(
diff --git a/content/browser/permissions/permission_service_impl.cc b/content/browser/permissions/permission_service_impl.cc
index 21d9ab9..fcf5ec0 100644
--- a/content/browser/permissions/permission_service_impl.cc
+++ b/content/browser/permissions/permission_service_impl.cc
@@ -104,7 +104,7 @@
   std::vector<PermissionType> types(permissions.size());
   std::set<PermissionType> duplicates_check;
   for (size_t i = 0; i < types.size(); ++i) {
-    auto type = PermissionDescriptorToPermissionType(permissions[i]);
+    auto type = blink::PermissionDescriptorToPermissionType(permissions[i]);
     if (!type) {
       ReceivedBadMessage();
       return;
@@ -147,7 +147,8 @@
 void PermissionServiceImpl::RevokePermission(
     PermissionDescriptorPtr permission,
     PermissionStatusCallback callback) {
-  auto permission_type = PermissionDescriptorToPermissionType(permission);
+  auto permission_type =
+      blink::PermissionDescriptorToPermissionType(permission);
   if (!permission_type) {
     ReceivedBadMessage();
     return;
@@ -170,7 +171,7 @@
     PermissionDescriptorPtr permission,
     PermissionStatus last_known_status,
     mojo::PendingRemote<blink::mojom::PermissionObserver> observer) {
-  auto type = PermissionDescriptorToPermissionType(permission);
+  auto type = blink::PermissionDescriptorToPermissionType(permission);
   if (!type) {
     ReceivedBadMessage();
     return;
@@ -182,7 +183,7 @@
 
 PermissionStatus PermissionServiceImpl::GetPermissionStatus(
     const PermissionDescriptorPtr& permission) {
-  auto type = PermissionDescriptorToPermissionType(permission);
+  auto type = blink::PermissionDescriptorToPermissionType(permission);
   if (!type) {
     ReceivedBadMessage();
     return PermissionStatus::DENIED;
diff --git a/content/browser/permissions/permission_service_impl.h b/content/browser/permissions/permission_service_impl.h
index f47d95a9..21a3350 100644
--- a/content/browser/permissions/permission_service_impl.h
+++ b/content/browser/permissions/permission_service_impl.h
@@ -10,14 +10,13 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/permissions/permission_service_context.h"
+#include "content/public/browser/permission_type.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom.h"
 #include "url/origin.h"
 
 namespace content {
 
-enum class PermissionType;
-
 // Implements the PermissionService Mojo interface.
 // This service can be created from a RenderFrameHost or a RenderProcessHost.
 // It is owned by a PermissionServiceContext.
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 4f1b2d3e..847002ec 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -284,7 +284,6 @@
     "permission_controller.h",
     "permission_controller_delegate.cc",
     "permission_controller_delegate.h",
-    "permission_type.cc",
     "permission_type.h",
     "picture_in_picture_window_controller.h",
     "platform_notification_context.h",
@@ -527,10 +526,6 @@
     deps += [ "//ppapi/c" ]
   }
 
-  if (is_chromeos || is_android || is_win || is_chromecast || is_fuchsia) {
-    defines = [ "ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION" ]
-  }
-
   if (is_posix || is_fuchsia) {
     sources += [ "posix_file_descriptor_info.h" ]
   }
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 41d5f73a..29f73d1 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -32,6 +32,7 @@
 #include "content/public/browser/generated_code_cache_settings.h"
 #include "content/public/browser/login_delegate.h"
 #include "content/public/browser/mojo_binder_policy_map.h"
+#include "content/public/browser/permission_type.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
 #include "content/public/common/main_function_params.h"
@@ -193,7 +194,6 @@
 }  // namespace storage
 
 namespace content {
-enum class PermissionType;
 enum class SiteIsolationMode;
 enum class SmsFetchFailureType;
 class AuthenticatorRequestClientDelegate;
diff --git a/content/public/browser/devtools_permission_overrides.cc b/content/public/browser/devtools_permission_overrides.cc
index a2c79c4..090bbbc4 100644
--- a/content/public/browser/devtools_permission_overrides.cc
+++ b/content/public/browser/devtools_permission_overrides.cc
@@ -72,7 +72,7 @@
     const absl::optional<url::Origin>& origin,
     const std::vector<PermissionType>& permissions) {
   const std::vector<PermissionType>& kAllPermissionTypes =
-      GetAllPermissionTypes();
+      blink::GetAllPermissionTypes();
   PermissionOverrides granted_overrides;
   for (const auto& permission : kAllPermissionTypes)
     granted_overrides[permission] = PermissionStatus::DENIED;
diff --git a/content/public/browser/permission_controller_delegate.h b/content/public/browser/permission_controller_delegate.h
index 0adaa67..88e7c00 100644
--- a/content/public/browser/permission_controller_delegate.h
+++ b/content/public/browser/permission_controller_delegate.h
@@ -13,7 +13,6 @@
 class GURL;
 
 namespace content {
-enum class PermissionType;
 class RenderFrameHost;
 class RenderProcessHost;
 
diff --git a/content/public/browser/permission_type.cc b/content/public/browser/permission_type.cc
deleted file mode 100644
index df8afa72..0000000
--- a/content/public/browser/permission_type.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/public/browser/permission_type.h"
-
-#include "base/no_destructor.h"
-#include "build/build_config.h"
-#include "third_party/blink/public/mojom/permissions/permission.mojom.h"
-
-using blink::mojom::PermissionDescriptorPtr;
-using blink::mojom::PermissionName;
-using blink::mojom::PermissionStatus;
-
-namespace content {
-
-const std::vector<PermissionType>& GetAllPermissionTypes() {
-  static const base::NoDestructor<std::vector<PermissionType>>
-      kAllPermissionTypes([] {
-        const int NUM_TYPES = static_cast<int>(PermissionType::NUM);
-        std::vector<PermissionType> all_types;
-        // Note: Update this if the set of removed entries changes.
-        // This is 6 because it skips 0 as well as the 5 numbers explicitly
-        // mentioned below.
-        all_types.reserve(NUM_TYPES - 6);
-        for (int i = 1; i < NUM_TYPES; ++i) {
-          // Skip removed entries.
-          if (i == 2 || i == 11 || i == 14 || i == 15 || i == 32)
-            continue;
-          all_types.push_back(static_cast<PermissionType>(i));
-        }
-        return all_types;
-      }());
-  return *kAllPermissionTypes;
-}
-
-absl::optional<PermissionType> PermissionDescriptorToPermissionType(
-    const PermissionDescriptorPtr& descriptor) {
-  switch (descriptor->name) {
-    case PermissionName::GEOLOCATION:
-      return PermissionType::GEOLOCATION;
-    case PermissionName::NOTIFICATIONS:
-      return PermissionType::NOTIFICATIONS;
-    case PermissionName::MIDI: {
-      if (descriptor->extension && descriptor->extension->is_midi() &&
-          descriptor->extension->get_midi()->sysex) {
-        return PermissionType::MIDI_SYSEX;
-      }
-      return PermissionType::MIDI;
-    }
-    case PermissionName::PROTECTED_MEDIA_IDENTIFIER:
-#if defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
-      return PermissionType::PROTECTED_MEDIA_IDENTIFIER;
-#else
-      NOTIMPLEMENTED();
-      return absl::nullopt;
-#endif  // defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
-    case PermissionName::DURABLE_STORAGE:
-      return PermissionType::DURABLE_STORAGE;
-    case PermissionName::AUDIO_CAPTURE:
-      return PermissionType::AUDIO_CAPTURE;
-    case PermissionName::VIDEO_CAPTURE:
-      if (descriptor->extension && descriptor->extension->is_camera_device() &&
-          descriptor->extension->get_camera_device()->panTiltZoom) {
-        return PermissionType::CAMERA_PAN_TILT_ZOOM;
-      } else {
-        return PermissionType::VIDEO_CAPTURE;
-      }
-    case PermissionName::BACKGROUND_SYNC:
-      return PermissionType::BACKGROUND_SYNC;
-    case PermissionName::SENSORS:
-      return PermissionType::SENSORS;
-    case PermissionName::ACCESSIBILITY_EVENTS:
-      return PermissionType::ACCESSIBILITY_EVENTS;
-    case PermissionName::CLIPBOARD_READ:
-      return PermissionType::CLIPBOARD_READ_WRITE;
-    case PermissionName::CLIPBOARD_WRITE: {
-      if (descriptor->extension && descriptor->extension->is_clipboard() &&
-          descriptor->extension->get_clipboard()->allowWithoutSanitization) {
-        return PermissionType::CLIPBOARD_READ_WRITE;
-      } else {
-        return PermissionType::CLIPBOARD_SANITIZED_WRITE;
-      }
-    }
-    case PermissionName::PAYMENT_HANDLER:
-      return PermissionType::PAYMENT_HANDLER;
-    case PermissionName::BACKGROUND_FETCH:
-      return PermissionType::BACKGROUND_FETCH;
-    case PermissionName::IDLE_DETECTION:
-      return PermissionType::IDLE_DETECTION;
-    case PermissionName::PERIODIC_BACKGROUND_SYNC:
-      return PermissionType::PERIODIC_BACKGROUND_SYNC;
-    case PermissionName::SCREEN_WAKE_LOCK:
-      return PermissionType::WAKE_LOCK_SCREEN;
-    case PermissionName::SYSTEM_WAKE_LOCK:
-      return PermissionType::WAKE_LOCK_SYSTEM;
-    case PermissionName::NFC:
-      return PermissionType::NFC;
-    case PermissionName::STORAGE_ACCESS:
-      return PermissionType::STORAGE_ACCESS_GRANT;
-    case PermissionName::WINDOW_PLACEMENT:
-      return PermissionType::WINDOW_PLACEMENT;
-    case PermissionName::LOCAL_FONTS:
-      return PermissionType::LOCAL_FONTS;
-    case PermissionName::DISPLAY_CAPTURE:
-      return PermissionType::DISPLAY_CAPTURE;
-  }
-
-  NOTREACHED();
-  return absl::nullopt;
-}
-
-}  // namespace content
diff --git a/content/public/browser/permission_type.h b/content/public/browser/permission_type.h
index 260c510d..80ef36a 100644
--- a/content/public/browser/permission_type.h
+++ b/content/public/browser/permission_type.h
@@ -5,64 +5,11 @@
 #ifndef CONTENT_PUBLIC_BROWSER_PERMISSION_TYPE_H_
 #define CONTENT_PUBLIC_BROWSER_PERMISSION_TYPE_H_
 
-#include <vector>
-
-#include "content/common/content_export.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/permissions/permission.mojom-forward.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
 
 namespace content {
 
-// This enum is also used for UMA purposes, so it needs to adhere to
-// the UMA guidelines.
-// Make sure you update enums.xml and GetAllPermissionTypes if you add new
-// or deprecate permission types.
-// Never delete or reorder an entry; only add new entries
-// immediately before PermissionType::NUM
-enum class PermissionType {
-  MIDI_SYSEX = 1,
-  // PUSH_MESSAGING = 2,
-  NOTIFICATIONS = 3,
-  GEOLOCATION = 4,
-  PROTECTED_MEDIA_IDENTIFIER = 5,
-  MIDI = 6,
-  DURABLE_STORAGE = 7,
-  AUDIO_CAPTURE = 8,
-  VIDEO_CAPTURE = 9,
-  BACKGROUND_SYNC = 10,
-  // FLASH = 11,
-  SENSORS = 12,
-  ACCESSIBILITY_EVENTS = 13,
-  // CLIPBOARD_READ = 14, // Replaced by CLIPBOARD_READ_WRITE in M81.
-  // CLIPBOARD_WRITE = 15, // Replaced by CLIPBOARD_SANITIZED_WRITE in M81.
-  PAYMENT_HANDLER = 16,
-  BACKGROUND_FETCH = 17,
-  IDLE_DETECTION = 18,
-  PERIODIC_BACKGROUND_SYNC = 19,
-  WAKE_LOCK_SCREEN = 20,
-  WAKE_LOCK_SYSTEM = 21,
-  NFC = 22,
-  CLIPBOARD_READ_WRITE = 23,
-  CLIPBOARD_SANITIZED_WRITE = 24,
-  VR = 25,
-  AR = 26,
-  STORAGE_ACCESS_GRANT = 27,
-  CAMERA_PAN_TILT_ZOOM = 28,
-  WINDOW_PLACEMENT = 29,
-  LOCAL_FONTS = 30,
-  DISPLAY_CAPTURE = 31,
-  // FILE_HANDLING = 32,  // Removed in M98.
-
-  // Always keep this at the end.
-  NUM,
-};
-
-CONTENT_EXPORT const std::vector<PermissionType>& GetAllPermissionTypes();
-
-// Given |descriptor|, set |permission_type| to a corresponding PermissionType.
-CONTENT_EXPORT absl::optional<PermissionType>
-PermissionDescriptorToPermissionType(
-    const blink::mojom::PermissionDescriptorPtr& descriptor);
+using PermissionType = blink::BlinkPermissionType;
 
 }  // namespace content
 
diff --git a/content/public/test/mock_permission_controller.h b/content/public/test/mock_permission_controller.h
index 375fd762..3328ef2 100644
--- a/content/public/test/mock_permission_controller.h
+++ b/content/public/test/mock_permission_controller.h
@@ -6,6 +6,7 @@
 #define CONTENT_PUBLIC_TEST_MOCK_PERMISSION_CONTROLLER_H_
 
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_type.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 class GURL;
@@ -16,8 +17,6 @@
 
 namespace content {
 
-enum class PermissionType;
-
 // Mock of the permission controller for unit tests.
 class MockPermissionController : public PermissionController {
  public:
diff --git a/content/public/test/mock_permission_manager.h b/content/public/test/mock_permission_manager.h
index 6cacf2f..7bd2d42b 100644
--- a/content/public/test/mock_permission_manager.h
+++ b/content/public/test/mock_permission_manager.h
@@ -6,13 +6,12 @@
 #define CONTENT_PUBLIC_TEST_MOCK_PERMISSION_MANAGER_H_
 
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_type.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "url/gurl.h"
 
 namespace content {
 
-enum class PermissionType;
-
 // Mock of the permission manager for unit tests.
 class MockPermissionManager : public PermissionControllerDelegate {
  public:
diff --git a/content/public/test/permission_type_unittest.cc b/content/public/test/permission_type_unittest.cc
index ae2c48ce..f0573630 100644
--- a/content/public/test/permission_type_unittest.cc
+++ b/content/public/test/permission_type_unittest.cc
@@ -12,7 +12,7 @@
 using testing::Contains;
 
 TEST(PermissionTypeHelpersTest, AllPermissionTypesSmokeTest) {
-  const auto all_permission_types = GetAllPermissionTypes();
+  const auto all_permission_types = blink::GetAllPermissionTypes();
 
   // All but PermissionType::NUM should be added.
   EXPECT_EQ(all_permission_types.size(),
diff --git a/content/public/test/permissions_test_utils.h b/content/public/test/permissions_test_utils.h
index c5c4c807..1bac2c5 100644
--- a/content/public/test/permissions_test_utils.h
+++ b/content/public/test/permissions_test_utils.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_PUBLIC_TEST_PERMISSIONS_TEST_UTILS_H_
 #define CONTENT_PUBLIC_TEST_PERMISSIONS_TEST_UTILS_H_
 
+#include "content/public/browser/permission_type.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
 namespace url {
@@ -14,7 +15,6 @@
 namespace content {
 
 class PermissionController;
-enum class PermissionType;
 
 void SetPermissionControllerOverrideForDevTools(
     PermissionController* permission_controller,
diff --git a/content/web_test/browser/web_test_permission_manager.cc b/content/web_test/browser/web_test_permission_manager.cc
index 3014378..9800029 100644
--- a/content/web_test/browser/web_test_permission_manager.cc
+++ b/content/web_test/browser/web_test_permission_manager.cc
@@ -250,7 +250,7 @@
     const GURL& url,
     const GURL& embedding_url,
     blink::test::mojom::PermissionAutomation::SetPermissionCallback callback) {
-  auto type = PermissionDescriptorToPermissionType(descriptor);
+  auto type = blink::PermissionDescriptorToPermissionType(descriptor);
   if (!type) {
     std::move(callback).Run(false);
     return;
diff --git a/third_party/blink/common/BUILD.gn b/third_party/blink/common/BUILD.gn
index 13f70d4..76e68c1 100644
--- a/third_party/blink/common/BUILD.gn
+++ b/third_party/blink/common/BUILD.gn
@@ -282,6 +282,10 @@
     sources += [ "dwrite_rasterizer_support/dwrite_rasterizer_support.cc" ]
     deps += [ "//ui/gfx/" ]
   }
+
+  if (is_chromeos || is_android || is_win || is_chromecast || is_fuchsia) {
+    defines = [ "ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION" ]
+  }
 }
 
 test("blink_common_unittests") {
diff --git a/third_party/blink/common/permissions/permission_utils.cc b/third_party/blink/common/permissions/permission_utils.cc
index bad6007..e9bf0a2 100644
--- a/third_party/blink/common/permissions/permission_utils.cc
+++ b/third_party/blink/common/permissions/permission_utils.cc
@@ -4,10 +4,16 @@
 
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 
+#include "base/no_destructor.h"
 #include "base/notreached.h"
+#include "third_party/blink/public/mojom/permissions/permission.mojom.h"
 
 namespace blink {
 
+using mojom::PermissionDescriptorPtr;
+using mojom::PermissionName;
+using mojom::PermissionStatus;
+
 mojom::PermissionStatus ToPermissionStatus(const std::string& status) {
   if (status == "granted")
     return mojom::PermissionStatus::GRANTED;
@@ -19,4 +25,112 @@
   return mojom::PermissionStatus::DENIED;
 }
 
+const std::vector<BlinkPermissionType>& GetAllPermissionTypes() {
+  static const base::NoDestructor<std::vector<BlinkPermissionType>>
+      kAllBlinkPermissionTypes([] {
+        const int NUM_TYPES = static_cast<int>(BlinkPermissionType::NUM);
+        std::vector<BlinkPermissionType> all_types;
+        // Note: Update this if the set of removed entries changes.
+        // This is 6 because it skips 0 as well as the 5 numbers explicitly
+        // mentioned below.
+        all_types.reserve(NUM_TYPES - 6);
+        for (int i = 1; i < NUM_TYPES; ++i) {
+          // Skip removed entries.
+          if (i == 2 || i == 11 || i == 14 || i == 15 || i == 32)
+            continue;
+          all_types.push_back(static_cast<BlinkPermissionType>(i));
+        }
+        return all_types;
+      }());
+  return *kAllBlinkPermissionTypes;
+}
+
+absl::optional<BlinkPermissionType> PermissionDescriptorToPermissionType(
+    const PermissionDescriptorPtr& descriptor) {
+  return PermissionDescriptorInfoToPermissionType(
+      descriptor->name,
+      descriptor->extension && descriptor->extension->is_midi() &&
+          descriptor->extension->get_midi()->sysex,
+      descriptor->extension && descriptor->extension->is_camera_device() &&
+          descriptor->extension->get_camera_device()->panTiltZoom,
+      descriptor->extension && descriptor->extension->is_clipboard() &&
+          descriptor->extension->get_clipboard()->allowWithoutSanitization);
+}
+
+absl::optional<BlinkPermissionType> PermissionDescriptorInfoToPermissionType(
+    mojom::PermissionName name,
+    bool midi_sysex,
+    bool camera_ptz,
+    bool clipboard_allow_without_sanitization) {
+  switch (name) {
+    case PermissionName::GEOLOCATION:
+      return BlinkPermissionType::GEOLOCATION;
+    case PermissionName::NOTIFICATIONS:
+      return BlinkPermissionType::NOTIFICATIONS;
+    case PermissionName::MIDI: {
+      if (midi_sysex) {
+        return BlinkPermissionType::MIDI_SYSEX;
+      }
+      return BlinkPermissionType::MIDI;
+    }
+    case PermissionName::PROTECTED_MEDIA_IDENTIFIER:
+#if defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
+      return BlinkPermissionType::PROTECTED_MEDIA_IDENTIFIER;
+#else
+      NOTIMPLEMENTED();
+      return absl::nullopt;
+#endif  // defined(ENABLE_PROTECTED_MEDIA_IDENTIFIER_PERMISSION)
+    case PermissionName::DURABLE_STORAGE:
+      return BlinkPermissionType::DURABLE_STORAGE;
+    case PermissionName::AUDIO_CAPTURE:
+      return BlinkPermissionType::AUDIO_CAPTURE;
+    case PermissionName::VIDEO_CAPTURE:
+      if (camera_ptz) {
+        return BlinkPermissionType::CAMERA_PAN_TILT_ZOOM;
+      } else {
+        return BlinkPermissionType::VIDEO_CAPTURE;
+      }
+    case PermissionName::BACKGROUND_SYNC:
+      return BlinkPermissionType::BACKGROUND_SYNC;
+    case PermissionName::SENSORS:
+      return BlinkPermissionType::SENSORS;
+    case PermissionName::ACCESSIBILITY_EVENTS:
+      return BlinkPermissionType::ACCESSIBILITY_EVENTS;
+    case PermissionName::CLIPBOARD_READ:
+      return BlinkPermissionType::CLIPBOARD_READ_WRITE;
+    case PermissionName::CLIPBOARD_WRITE: {
+      if (clipboard_allow_without_sanitization) {
+        return BlinkPermissionType::CLIPBOARD_READ_WRITE;
+      } else {
+        return BlinkPermissionType::CLIPBOARD_SANITIZED_WRITE;
+      }
+    }
+    case PermissionName::PAYMENT_HANDLER:
+      return BlinkPermissionType::PAYMENT_HANDLER;
+    case PermissionName::BACKGROUND_FETCH:
+      return BlinkPermissionType::BACKGROUND_FETCH;
+    case PermissionName::IDLE_DETECTION:
+      return BlinkPermissionType::IDLE_DETECTION;
+    case PermissionName::PERIODIC_BACKGROUND_SYNC:
+      return BlinkPermissionType::PERIODIC_BACKGROUND_SYNC;
+    case PermissionName::SCREEN_WAKE_LOCK:
+      return BlinkPermissionType::WAKE_LOCK_SCREEN;
+    case PermissionName::SYSTEM_WAKE_LOCK:
+      return BlinkPermissionType::WAKE_LOCK_SYSTEM;
+    case PermissionName::NFC:
+      return BlinkPermissionType::NFC;
+    case PermissionName::STORAGE_ACCESS:
+      return BlinkPermissionType::STORAGE_ACCESS_GRANT;
+    case PermissionName::WINDOW_PLACEMENT:
+      return BlinkPermissionType::WINDOW_PLACEMENT;
+    case PermissionName::LOCAL_FONTS:
+      return BlinkPermissionType::LOCAL_FONTS;
+    case PermissionName::DISPLAY_CAPTURE:
+      return BlinkPermissionType::DISPLAY_CAPTURE;
+
+      NOTREACHED();
+      return absl::nullopt;
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/permissions/permission_utils.h b/third_party/blink/public/common/permissions/permission_utils.h
index d623fc6..2350b84e 100644
--- a/third_party/blink/public/common/permissions/permission_utils.h
+++ b/third_party/blink/public/common/permissions/permission_utils.h
@@ -7,16 +7,85 @@
 
 #include <string>
 
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
+#include "third_party/blink/public/mojom/permissions/permission.mojom-forward.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
 
 namespace blink {
 
+// This enum is also used for UMA purposes, so it needs to adhere to
+// the UMA guidelines.
+// Make sure you update enums.xml and GetAllPermissionTypes if you add new
+// or deprecate permission types.
+// Never delete or reorder an entry; only add new entries
+// immediately before PermissionType::NUM
+enum class BlinkPermissionType {
+  MIDI_SYSEX = 1,
+  // PUSH_MESSAGING = 2,
+  NOTIFICATIONS = 3,
+  GEOLOCATION = 4,
+  PROTECTED_MEDIA_IDENTIFIER = 5,
+  MIDI = 6,
+  DURABLE_STORAGE = 7,
+  AUDIO_CAPTURE = 8,
+  VIDEO_CAPTURE = 9,
+  BACKGROUND_SYNC = 10,
+  // FLASH = 11,
+  SENSORS = 12,
+  ACCESSIBILITY_EVENTS = 13,
+  // CLIPBOARD_READ = 14, // Replaced by CLIPBOARD_READ_WRITE in M81.
+  // CLIPBOARD_WRITE = 15, // Replaced by CLIPBOARD_SANITIZED_WRITE in M81.
+  PAYMENT_HANDLER = 16,
+  BACKGROUND_FETCH = 17,
+  IDLE_DETECTION = 18,
+  PERIODIC_BACKGROUND_SYNC = 19,
+  WAKE_LOCK_SCREEN = 20,
+  WAKE_LOCK_SYSTEM = 21,
+  NFC = 22,
+  CLIPBOARD_READ_WRITE = 23,
+  CLIPBOARD_SANITIZED_WRITE = 24,
+  VR = 25,
+  AR = 26,
+  STORAGE_ACCESS_GRANT = 27,
+  CAMERA_PAN_TILT_ZOOM = 28,
+  WINDOW_PLACEMENT = 29,
+  LOCAL_FONTS = 30,
+  DISPLAY_CAPTURE = 31,
+  // FILE_HANDLING = 32,  // Removed in M98.
+
+  // Always keep this at the end.
+  NUM,
+};
+
 // Converts a permission string ("granted", "denied", "prompt") into a
 // PermissionStatus.
 BLINK_COMMON_EXPORT mojom::PermissionStatus ToPermissionStatus(
     const std::string& status);
 
+// Get a list of all permission types.
+BLINK_COMMON_EXPORT const std::vector<BlinkPermissionType>&
+GetAllPermissionTypes();
+
+// Given |descriptor|, set |permission_type| to a corresponding PermissionType.
+BLINK_COMMON_EXPORT absl::optional<BlinkPermissionType>
+PermissionDescriptorToPermissionType(
+    const mojom::PermissionDescriptorPtr& descriptor);
+
+// Ideally this would be an equivalent function to
+// |PermissionDescriptorToPermissionType| but for a
+// `mojom::blink::PermissionDescriptorPtr` descriptor. But unfortunately mojo
+// blink headers depend on blink/common so we can't introduce the reverse
+// dependency. Instead we provide this function that requires the relevant
+// information for making the decision and the caller needs to extract it from
+// the descriptor and provide it.
+BLINK_COMMON_EXPORT absl::optional<BlinkPermissionType>
+PermissionDescriptorInfoToPermissionType(
+    mojom::PermissionName name,
+    bool midi_sysex,
+    bool camera_ptz,
+    bool clipboard_allow_without_sanitization);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_PERMISSIONS_PERMISSION_UTILS_H_
diff --git a/third_party/blink/renderer/modules/permissions/BUILD.gn b/third_party/blink/renderer/modules/permissions/BUILD.gn
index 2f90b5ae..0e54957 100644
--- a/third_party/blink/renderer/modules/permissions/BUILD.gn
+++ b/third_party/blink/renderer/modules/permissions/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "permission_status.cc",
     "permission_status.h",
+    "permission_status_listener.cc",
+    "permission_status_listener.h",
     "permission_utils.cc",
     "permission_utils.h",
     "permissions.cc",
diff --git a/third_party/blink/renderer/modules/permissions/permission_status.cc b/third_party/blink/renderer/modules/permissions/permission_status.cc
index d74aca8..6a5abea 100644
--- a/third_party/blink/renderer/modules/permissions/permission_status.cc
+++ b/third_party/blink/renderer/modules/permissions/permission_status.cc
@@ -4,53 +4,30 @@
 
 #include "third_party/blink/renderer/modules/permissions/permission_status.h"
 
-#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/frame/lifecycle.mojom-shared.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
-#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/modules/event_target_modules_names.h"
-#include "third_party/blink/renderer/modules/permissions/permission_utils.h"
-#include "third_party/blink/renderer/modules/permissions/permissions.h"
-#include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/modules/permissions/permission_status_listener.h"
 
 namespace blink {
 
 // static
-PermissionStatus* PermissionStatus::Take(
-    Permissions& associated_permissions_object,
-    ScriptPromiseResolver* resolver,
-    MojoPermissionStatus status,
-    MojoPermissionDescriptor descriptor) {
-  return PermissionStatus::CreateAndListen(associated_permissions_object,
-                                           resolver->GetExecutionContext(),
-                                           status, std::move(descriptor));
-}
-
-PermissionStatus* PermissionStatus::CreateAndListen(
-    Permissions& associated_permissions_object,
-    ExecutionContext* execution_context,
-    MojoPermissionStatus status,
-    MojoPermissionDescriptor descriptor) {
-  PermissionStatus* permission_status = MakeGarbageCollected<PermissionStatus>(
-      associated_permissions_object, execution_context, status,
-      std::move(descriptor));
+PermissionStatus* PermissionStatus::Take(PermissionStatusListener* listener,
+                                         ScriptPromiseResolver* resolver) {
+  ExecutionContext* execution_context = resolver->GetExecutionContext();
+  PermissionStatus* permission_status =
+      MakeGarbageCollected<PermissionStatus>(listener, execution_context);
   permission_status->UpdateStateIfNeeded();
   permission_status->StartListening();
   return permission_status;
 }
 
-PermissionStatus::PermissionStatus(Permissions& associated_permissions_object,
-                                   ExecutionContext* execution_context,
-                                   MojoPermissionStatus status,
-                                   MojoPermissionDescriptor descriptor)
+PermissionStatus::PermissionStatus(PermissionStatusListener* listener,
+                                   ExecutionContext* execution_context)
     : ExecutionContextLifecycleStateObserver(execution_context),
-      status_(status),
-      descriptor_(std::move(descriptor)),
-      receiver_(this, execution_context) {
-  associated_permissions_object.PermissionStatusObjectCreated();
-}
+      listener_(listener) {}
 
 PermissionStatus::~PermissionStatus() = default;
 
@@ -63,7 +40,9 @@
 }
 
 bool PermissionStatus::HasPendingActivity() const {
-  return receiver_.is_bound();
+  if (!listener_)
+    return false;
+  return listener_->HasPendingActivity();
 }
 
 void PermissionStatus::ContextLifecycleStateChanged(
@@ -75,43 +54,38 @@
 }
 
 String PermissionStatus::state() const {
-  return PermissionStatusToString(status_);
+  if (!listener_)
+    return String();
+  return listener_->state();
 }
 
 String PermissionStatus::name() const {
-  return PermissionNameToString(descriptor_->name);
+  if (!listener_)
+    return String();
+  return listener_->name();
 }
 
 void PermissionStatus::StartListening() {
-  DCHECK(!receiver_.is_bound());
-  mojo::PendingRemote<mojom::blink::PermissionObserver> observer;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
-      GetExecutionContext()->GetTaskRunner(TaskType::kPermission);
-  receiver_.Bind(observer.InitWithNewPipeAndPassReceiver(), task_runner);
-
-  mojo::Remote<mojom::blink::PermissionService> service;
-  ConnectToPermissionService(GetExecutionContext(),
-                             service.BindNewPipeAndPassReceiver(task_runner));
-  service->AddPermissionObserver(descriptor_->Clone(), status_,
-                                 std::move(observer));
+  if (!listener_)
+    return;
+  listener_->AddObserver(this);
 }
 
 void PermissionStatus::StopListening() {
-  receiver_.reset();
+  if (!listener_)
+    return;
+  listener_->RemoveObserver(this);
 }
 
 void PermissionStatus::OnPermissionStatusChange(MojoPermissionStatus status) {
-  if (status_ == status)
-    return;
-
-  status_ = status;
   DispatchEvent(*Event::Create(event_type_names::kChange));
 }
 
 void PermissionStatus::Trace(Visitor* visitor) const {
-  visitor->Trace(receiver_);
+  visitor->Trace(listener_);
   EventTargetWithInlineData::Trace(visitor);
   ExecutionContextLifecycleStateObserver::Trace(visitor);
+  PermissionStatusListener::Observer::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/permissions/permission_status.h b/third_party/blink/renderer/modules/permissions/permission_status.h
index 12cfa6c8..59f84ab0 100644
--- a/third_party/blink/renderer/modules/permissions/permission_status.h
+++ b/third_party/blink/renderer/modules/permissions/permission_status.h
@@ -5,10 +5,10 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PERMISSIONS_PERMISSION_STATUS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_PERMISSIONS_PERMISSION_STATUS_H_
 
-#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_state_observer.h"
+#include "third_party/blink/renderer/modules/permissions/permission_status_listener.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
@@ -18,34 +18,23 @@
 
 class ExecutionContext;
 class ScriptPromiseResolver;
-class Permissions;
 
 // Expose the status of a given permission type for the current
 // ExecutionContext.
 class PermissionStatus final : public EventTargetWithInlineData,
                                public ActiveScriptWrappable<PermissionStatus>,
                                public ExecutionContextLifecycleStateObserver,
-                               public mojom::blink::PermissionObserver {
+                               public PermissionStatusListener::Observer {
   DEFINE_WRAPPERTYPEINFO();
 
   using MojoPermissionDescriptor = mojom::blink::PermissionDescriptorPtr;
   using MojoPermissionStatus = mojom::blink::PermissionStatus;
 
  public:
-  static PermissionStatus* Take(Permissions&,
-                                ScriptPromiseResolver*,
-                                MojoPermissionStatus,
-                                MojoPermissionDescriptor);
+  static PermissionStatus* Take(PermissionStatusListener*,
+                                ScriptPromiseResolver*);
 
-  static PermissionStatus* CreateAndListen(Permissions&,
-                                           ExecutionContext*,
-                                           MojoPermissionStatus,
-                                           MojoPermissionDescriptor);
-
-  PermissionStatus(Permissions&,
-                   ExecutionContext*,
-                   MojoPermissionStatus,
-                   MojoPermissionDescriptor);
+  PermissionStatus(PermissionStatusListener*, ExecutionContext*);
   ~PermissionStatus() override;
 
   // EventTarget implementation.
@@ -59,6 +48,9 @@
   void ContextLifecycleStateChanged(mojom::FrameLifecycleState) override;
   void ContextDestroyed() override {}
 
+  // PermissionStatusListener::Observer
+  void OnPermissionStatusChange(MojoPermissionStatus) override;
+
   String state() const;
 
   String name() const;
@@ -71,12 +63,7 @@
   void StartListening();
   void StopListening();
 
-  void OnPermissionStatusChange(MojoPermissionStatus) override;
-
-  MojoPermissionStatus status_;
-  MojoPermissionDescriptor descriptor_;
-  HeapMojoReceiver<mojom::blink::PermissionObserver, PermissionStatus>
-      receiver_;
+  WeakMember<PermissionStatusListener> listener_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/permissions/permission_status_listener.cc b/third_party/blink/renderer/modules/permissions/permission_status_listener.cc
new file mode 100644
index 0000000..424314c
--- /dev/null
+++ b/third_party/blink/renderer/modules/permissions/permission_status_listener.cc
@@ -0,0 +1,105 @@
+// Copyright 2021 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 "third_party/blink/renderer/modules/permissions/permission_status_listener.h"
+
+#include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
+#include "third_party/blink/renderer/modules/permissions/permission_utils.h"
+#include "third_party/blink/renderer/modules/permissions/permissions.h"
+
+namespace blink {
+
+PermissionStatusListener* PermissionStatusListener::Create(
+    Permissions& associated_permissions_object,
+    ExecutionContext* execution_context,
+    MojoPermissionStatus status,
+    MojoPermissionDescriptor descriptor) {
+  PermissionStatusListener* permission_status =
+      MakeGarbageCollected<PermissionStatusListener>(
+          associated_permissions_object, execution_context, status,
+          std::move(descriptor));
+  return permission_status;
+}
+
+PermissionStatusListener::PermissionStatusListener(
+    Permissions& associated_permissions_object,
+    ExecutionContext* execution_context,
+    MojoPermissionStatus status,
+    MojoPermissionDescriptor descriptor)
+    : ExecutionContextClient(execution_context),
+      status_(status),
+      descriptor_(std::move(descriptor)),
+      receiver_(this, execution_context) {
+  associated_permissions_object.PermissionStatusObjectCreated();
+}
+
+PermissionStatusListener::~PermissionStatusListener() = default;
+
+void PermissionStatusListener::StartListening() {
+  DCHECK(!receiver_.is_bound());
+  mojo::PendingRemote<mojom::blink::PermissionObserver> observer;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+      GetExecutionContext()->GetTaskRunner(TaskType::kPermission);
+  receiver_.Bind(observer.InitWithNewPipeAndPassReceiver(), task_runner);
+
+  mojo::Remote<mojom::blink::PermissionService> service;
+  ConnectToPermissionService(GetExecutionContext(),
+                             service.BindNewPipeAndPassReceiver(task_runner));
+  service->AddPermissionObserver(descriptor_->Clone(), status_,
+                                 std::move(observer));
+}
+
+void PermissionStatusListener::StopListening() {
+  receiver_.reset();
+}
+
+void PermissionStatusListener::OnPermissionStatusChange(
+    MojoPermissionStatus status) {
+  if (status_ == status)
+    return;
+
+  status_ = status;
+
+  for (const auto& observer : observers_) {
+    if (observer)
+      observer->OnPermissionStatusChange(status);
+    else
+      RemoveObserver(observer);
+  }
+}
+
+void PermissionStatusListener::AddObserver(Observer* observer) {
+  if (observers_.IsEmpty())
+    StartListening();
+
+  observers_.insert(observer);
+}
+
+void PermissionStatusListener::RemoveObserver(Observer* observer) {
+  observers_.erase(observer);
+
+  if (observers_.IsEmpty())
+    StopListening();
+}
+
+bool PermissionStatusListener::HasPendingActivity() {
+  return receiver_.is_bound();
+}
+
+String PermissionStatusListener::state() const {
+  return PermissionStatusToString(status_);
+}
+
+String PermissionStatusListener::name() const {
+  return PermissionNameToString(descriptor_->name);
+}
+
+void PermissionStatusListener::Trace(Visitor* visitor) const {
+  visitor->Trace(observers_);
+  visitor->Trace(receiver_);
+  ExecutionContextClient::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/permissions/permission_status_listener.h b/third_party/blink/renderer/modules/permissions/permission_status_listener.h
new file mode 100644
index 0000000..2b906cab
--- /dev/null
+++ b/third_party/blink/renderer/modules/permissions/permission_status_listener.h
@@ -0,0 +1,79 @@
+// Copyright 2021 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 THIRD_PARTY_BLINK_RENDERER_MODULES_PERMISSIONS_PERMISSION_STATUS_LISTENER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PERMISSIONS_PERMISSION_STATUS_LISTENER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
+
+namespace blink {
+
+class ExecutionContext;
+class Permissions;
+
+class PermissionStatusListener final
+    : public GarbageCollected<PermissionStatusListener>,
+      public ExecutionContextClient,
+      public mojom::blink::PermissionObserver {
+  using MojoPermissionDescriptor = mojom::blink::PermissionDescriptorPtr;
+  using MojoPermissionStatus = mojom::blink::PermissionStatus;
+
+ public:
+  class Observer : public GarbageCollectedMixin {
+   public:
+    virtual ~Observer() = default;
+
+    virtual void OnPermissionStatusChange(MojoPermissionStatus) = 0;
+
+    void Trace(Visitor* visitor) const override {}
+  };
+
+  static PermissionStatusListener* Create(Permissions&,
+                                          ExecutionContext*,
+                                          MojoPermissionStatus,
+                                          MojoPermissionDescriptor);
+
+  PermissionStatusListener(Permissions&,
+                           ExecutionContext*,
+                           MojoPermissionStatus,
+                           MojoPermissionDescriptor);
+  ~PermissionStatusListener() override;
+
+  PermissionStatusListener(const PermissionStatusListener&) = delete;
+  PermissionStatusListener& operator=(const PermissionStatusListener&) = delete;
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  bool HasPendingActivity();
+  void SetStatus(MojoPermissionStatus status) { status_ = status; }
+
+  String state() const;
+  String name() const;
+
+  void Trace(Visitor*) const override;
+
+ private:
+  void StartListening();
+  void StopListening();
+
+  // mojom::blink::PermissionObserver
+  void OnPermissionStatusChange(MojoPermissionStatus) override;
+
+  MojoPermissionStatus status_;
+  MojoPermissionDescriptor descriptor_;
+  HeapHashSet<WeakMember<Observer>> observers_;
+  HeapMojoReceiver<mojom::blink::PermissionObserver, PermissionStatusListener>
+      receiver_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PERMISSIONS_PERMISSION_STATUS_LISTENER_H_
diff --git a/third_party/blink/renderer/modules/permissions/permissions.cc b/third_party/blink/renderer/modules/permissions/permissions.cc
index 6a29853..5b14c7acd 100644
--- a/third_party/blink/renderer/modules/permissions/permissions.cc
+++ b/third_party/blink/renderer/modules/permissions/permissions.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/metrics/histogram_functions.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -90,10 +91,12 @@
   PermissionDescriptorPtr descriptor_copy = descriptor->Clone();
   LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context);
   LocalFrame* frame = window ? window->GetFrame() : nullptr;
+
   GetService(context)->RequestPermission(
       std::move(descriptor), LocalFrame::HasTransientUserActivation(frame),
-      WTF::Bind(&Permissions::TaskComplete, WrapPersistent(this),
-                WrapPersistent(resolver), std::move(descriptor_copy)));
+      WTF::Bind(&Permissions::VerifyPermissionAndReturnStatus,
+                WrapPersistent(this), WrapPersistent(resolver),
+                std::move(descriptor_copy)));
   return promise;
 }
 
@@ -160,12 +163,15 @@
 
   LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(context);
   LocalFrame* frame = window ? window->GetFrame() : nullptr;
+
   GetService(context)->RequestPermissions(
       std::move(internal_permissions),
       LocalFrame::HasTransientUserActivation(frame),
-      WTF::Bind(&Permissions::BatchTaskComplete, WrapPersistent(this),
-                WrapPersistent(resolver), std::move(internal_permissions_copy),
-                std::move(caller_index_to_internal_index)));
+      WTF::Bind(
+          &Permissions::VerifyPermissionsAndReturnStatus, WrapPersistent(this),
+          WrapPersistent(resolver), std::move(internal_permissions_copy),
+          std::move(caller_index_to_internal_index),
+          -1 /* last_verified_permission_index */, true /* is_bulk_request */));
   return promise;
 }
 
@@ -176,6 +182,7 @@
 
 void Permissions::Trace(Visitor* visitor) const {
   visitor->Trace(service_);
+  visitor->Trace(listeners_);
   ScriptWrappable::Trace(visitor);
   Supplement<NavigatorBase>::Trace(visitor);
   ExecutionContextLifecycleObserver::Trace(visitor);
@@ -204,15 +211,41 @@
   if (!resolver->GetExecutionContext() ||
       resolver->GetExecutionContext()->IsContextDestroyed())
     return;
-  resolver->Resolve(
-      PermissionStatus::Take(*this, resolver, result, std::move(descriptor)));
+
+  PermissionStatusListener* listener =
+      GetOrCreatePermissionStatusListener(result, std::move(descriptor));
+  if (listener)
+    resolver->Resolve(PermissionStatus::Take(listener, resolver));
 }
 
-void Permissions::BatchTaskComplete(
+void Permissions::VerifyPermissionAndReturnStatus(
+    ScriptPromiseResolver* resolver,
+    mojom::blink::PermissionDescriptorPtr descriptor,
+    mojom::blink::PermissionStatus result) {
+  Vector<int> caller_index_to_internal_index;
+  caller_index_to_internal_index.push_back(0);
+  Vector<mojom::blink::PermissionStatus> results;
+  results.push_back(std::move(result));
+  Vector<mojom::blink::PermissionDescriptorPtr> descriptors;
+  descriptors.push_back(std::move(descriptor));
+
+  VerifyPermissionsAndReturnStatus(resolver, std::move(descriptors),
+                                   std::move(caller_index_to_internal_index),
+                                   -1 /* last_verified_permission_index */,
+                                   false /* is_bulk_request */,
+                                   std::move(results));
+}
+
+void Permissions::VerifyPermissionsAndReturnStatus(
     ScriptPromiseResolver* resolver,
     Vector<mojom::blink::PermissionDescriptorPtr> descriptors,
     Vector<int> caller_index_to_internal_index,
+    int last_verified_permission_index,
+    bool is_bulk_request,
     const Vector<mojom::blink::PermissionStatus>& results) {
+  DCHECK(caller_index_to_internal_index.size() == 1u || is_bulk_request);
+  DCHECK_EQ(descriptors.size(), caller_index_to_internal_index.size());
+
   if (!resolver->GetExecutionContext() ||
       resolver->GetExecutionContext()->IsContextDestroyed())
     return;
@@ -223,11 +256,102 @@
   HeapVector<Member<PermissionStatus>> result;
   result.ReserveInitialCapacity(caller_index_to_internal_index.size());
   for (int internal_index : caller_index_to_internal_index) {
-    result.push_back(PermissionStatus::CreateAndListen(
-        *this, resolver->GetExecutionContext(), results[internal_index],
-        descriptors[internal_index]->Clone()));
+    // If there is a chance that this permission result came from a different
+    // permission type (e.g. a PTZ request could be replaced with a camera
+    // request internally), then re-check the actual permission type to ensure
+    // that it it indeed that permission type. If it's not, replace the
+    // descriptor with the verification descriptor.
+    auto verification_descriptor = CreatePermissionVerificationDescriptor(
+        *GetPermissionType(*descriptors[internal_index]));
+    if (last_verified_permission_index == -1 && verification_descriptor) {
+      auto descriptor_copy = descriptors[internal_index]->Clone();
+      service_->HasPermission(
+          std::move(descriptor_copy),
+          WTF::Bind(&Permissions::PermissionVerificationComplete,
+                    WrapPersistent(this), WrapPersistent(resolver),
+                    std::move(descriptors),
+                    std::move(caller_index_to_internal_index),
+                    std::move(results), std::move(verification_descriptor),
+                    internal_index, is_bulk_request));
+      return;
+    }
+
+    // This is the last permission that was verified.
+    if (internal_index == last_verified_permission_index)
+      last_verified_permission_index = -1;
+
+    PermissionStatusListener* listener = GetOrCreatePermissionStatusListener(
+        results[internal_index], descriptors[internal_index]->Clone());
+    if (listener) {
+      // If it's not a bulk request, return the first (and only) result.
+      if (!is_bulk_request) {
+        resolver->Resolve(PermissionStatus::Take(listener, resolver));
+        return;
+      }
+      result.push_back(PermissionStatus::Take(listener, resolver));
+    }
   }
   resolver->Resolve(result);
 }
 
+void Permissions::PermissionVerificationComplete(
+    ScriptPromiseResolver* resolver,
+    Vector<mojom::blink::PermissionDescriptorPtr> descriptors,
+    Vector<int> caller_index_to_internal_index,
+    const Vector<mojom::blink::PermissionStatus>& results,
+    mojom::blink::PermissionDescriptorPtr verification_descriptor,
+    int internal_index_to_verify,
+    bool is_bulk_request,
+    mojom::blink::PermissionStatus verification_result) {
+  if (verification_result != results[internal_index_to_verify]) {
+    // The permission actually came from the verification descriptor, so use
+    // that descriptor when returning the permission status.
+    descriptors[internal_index_to_verify] = std::move(verification_descriptor);
+  }
+
+  VerifyPermissionsAndReturnStatus(resolver, std::move(descriptors),
+                                   std::move(caller_index_to_internal_index),
+                                   internal_index_to_verify, is_bulk_request,
+                                   std::move(results));
+}
+
+PermissionStatusListener* Permissions::GetOrCreatePermissionStatusListener(
+    mojom::blink::PermissionStatus status,
+    mojom::blink::PermissionDescriptorPtr descriptor) {
+  auto type = GetPermissionType(*descriptor);
+  if (!type)
+    return nullptr;
+
+  if (!listeners_.Contains(*type)) {
+    listeners_.insert(
+        *type, PermissionStatusListener::Create(*this, GetExecutionContext(),
+                                                status, std::move(descriptor)));
+  } else {
+    listeners_.at(*type)->SetStatus(status);
+  }
+
+  return listeners_.at(*type);
+}
+
+absl::optional<BlinkPermissionType> Permissions::GetPermissionType(
+    const mojom::blink::PermissionDescriptor& descriptor) {
+  return PermissionDescriptorInfoToPermissionType(
+      descriptor.name,
+      descriptor.extension && descriptor.extension->is_midi() &&
+          descriptor.extension->get_midi()->sysex,
+      descriptor.extension && descriptor.extension->is_camera_device() &&
+          descriptor.extension->get_camera_device()->panTiltZoom,
+      descriptor.extension && descriptor.extension->is_clipboard() &&
+          descriptor.extension->get_clipboard()->allowWithoutSanitization);
+}
+
+mojom::blink::PermissionDescriptorPtr
+Permissions::CreatePermissionVerificationDescriptor(
+    BlinkPermissionType descriptor_type) {
+  if (descriptor_type == BlinkPermissionType::CAMERA_PAN_TILT_ZOOM) {
+    return CreateVideoCapturePermissionDescriptor(false /* pan_tilt_zoom */);
+  }
+  return nullptr;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/permissions/permissions.h b/third_party/blink/renderer/modules/permissions/permissions.h
index 2898e75..4e46c1d 100644
--- a/third_party/blink/renderer/modules/permissions/permissions.h
+++ b/third_party/blink/renderer/modules/permissions/permissions.h
@@ -9,11 +9,13 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
+#include "third_party/blink/renderer/modules/permissions/permission_status_listener.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
 
 namespace blink {
 
@@ -22,6 +24,7 @@
 class ScriptPromiseResolver;
 class ScriptState;
 class ScriptValue;
+enum class BlinkPermissionType;
 
 class Permissions final : public ScriptWrappable,
                           public Supplement<NavigatorBase>,
@@ -53,16 +56,45 @@
  private:
   mojom::blink::PermissionService* GetService(ExecutionContext*);
   void ServiceConnectionError();
-  void TaskComplete(ScriptPromiseResolver*,
-                    mojom::blink::PermissionDescriptorPtr,
-                    mojom::blink::PermissionStatus);
-  void BatchTaskComplete(ScriptPromiseResolver*,
-                         Vector<mojom::blink::PermissionDescriptorPtr>,
-                         Vector<int>,
-                         const Vector<mojom::blink::PermissionStatus>&);
+
+  void TaskComplete(ScriptPromiseResolver* resolver,
+                    mojom::blink::PermissionDescriptorPtr descriptor,
+                    mojom::blink::PermissionStatus result);
+
+  void VerifyPermissionAndReturnStatus(
+      ScriptPromiseResolver* resolver,
+      mojom::blink::PermissionDescriptorPtr descriptor,
+      mojom::blink::PermissionStatus result);
+  void VerifyPermissionsAndReturnStatus(
+      ScriptPromiseResolver* resolver,
+      Vector<mojom::blink::PermissionDescriptorPtr> descriptors,
+      Vector<int> caller_index_to_internal_index,
+      int last_verified_permission_index,
+      bool is_bulk_request,
+      const Vector<mojom::blink::PermissionStatus>& results);
+
+  void PermissionVerificationComplete(
+      ScriptPromiseResolver* resolver,
+      Vector<mojom::blink::PermissionDescriptorPtr> descriptors,
+      Vector<int> caller_index_to_internal_index,
+      const Vector<mojom::blink::PermissionStatus>& results,
+      mojom::blink::PermissionDescriptorPtr verification_descriptor,
+      int internal_index_to_verify,
+      bool is_bulk_request,
+      mojom::blink::PermissionStatus verification_result);
+
+  PermissionStatusListener* GetOrCreatePermissionStatusListener(
+      mojom::blink::PermissionStatus status,
+      mojom::blink::PermissionDescriptorPtr descriptor);
+  absl::optional<BlinkPermissionType> GetPermissionType(
+      const mojom::blink::PermissionDescriptor& descriptor);
+  mojom::blink::PermissionDescriptorPtr CreatePermissionVerificationDescriptor(
+      BlinkPermissionType descriptor_type);
 
   int created_permission_status_objects_ = 0;
 
+  HeapHashMap<BlinkPermissionType, Member<PermissionStatusListener>> listeners_;
+
   HeapMojoRemote<mojom::blink::PermissionService> service_;
 };
 
diff --git a/weblayer/browser/permissions/permission_manager_factory.cc b/weblayer/browser/permissions/permission_manager_factory.cc
index 13726db..950e3245 100644
--- a/weblayer/browser/permissions/permission_manager_factory.cc
+++ b/weblayer/browser/permissions/permission_manager_factory.cc
@@ -119,7 +119,7 @@
 
   // For now, all requests are denied. As features are added, their permission
   // contexts can be added here instead of DeniedPermissionContext.
-  for (content::PermissionType type : content::GetAllPermissionTypes()) {
+  for (content::PermissionType type : blink::GetAllPermissionTypes()) {
 #if !BUILDFLAG(IS_ANDROID)
     // PROTECTED_MEDIA_IDENTIFIER is only supported on Android.
     if (type == content::PermissionType::PROTECTED_MEDIA_IDENTIFIER)