Web API to read Chrome device attributes

To support the deprecation of Chrome App, we need to provide new web
APIs to replace the existing chrome.enterprise.deviceAttributes.

They are added into navigator.device.* namespace and only available to
trusted applications. (see go/web-api-navigator-device-dd)

PRD: go/pwa-api-devices
DD: go/pwa-api-devices-dd

Bug: 1132865
Change-Id: Ib01ec58c94843d70bebe1515ae0ffac7eb4f2272
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2505139
Commit-Queue: Anqing Zhao <[email protected]>
Reviewed-by: Sergey Poromov <[email protected]>
Reviewed-by: enne <[email protected]>
Reviewed-by: Anatoliy Potapchuk <[email protected]>
Reviewed-by: Daniel Cheng <[email protected]>
Cr-Commit-Position: refs/heads/master@{#845016}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 7a9c316..acd46d8d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3503,6 +3503,8 @@
       "content_settings/generated_notification_pref.h",
       "custom_handlers/register_protocol_handler_permission_request.cc",
       "custom_handlers/register_protocol_handler_permission_request.h",
+      "device_api/device_attribute_api.cc",
+      "device_api/device_attribute_api.h",
       "device_api/device_service_impl.cc",
       "device_api/device_service_impl.h",
       "device_api/managed_configuration_api.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1ba1da65..e79684f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6969,6 +6969,11 @@
      FEATURE_VALUE_TYPE(media::kWasapiRawAudioCapture)},
 #endif  // defined(OS_MAC)
 
+    {"enable-restricted-web-apis",
+     flag_descriptions::kEnableRestrictedWebApisName,
+     flag_descriptions::kEnableRestrictedWebApisDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(features::kEnableRestrictedWebApis)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/device_api/device_attribute_api.cc b/chrome/browser/device_api/device_attribute_api.cc
new file mode 100644
index 0000000..7828392
--- /dev/null
+++ b/chrome/browser/device_api/device_attribute_api.cc
@@ -0,0 +1,133 @@
+// 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 "chrome/browser/device_api/device_attribute_api.h"
+#include "build/chromeos_buildflags.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chromeos/system/statistics_provider.h"
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+#endif
+
+namespace device_attribute_api {
+
+namespace {
+
+using Result = blink::mojom::DeviceAttributeResult;
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+const char kNotSupportedPlatformErrorMessage[] =
+    "This restricted web API is not supported on the current platform.";
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+void AdaptLacrosResult(
+    DeviceAPIService::GetDirectoryIdCallback callback,
+    crosapi::mojom::DeviceAttributesStringResultPtr lacros_result) {
+  if (lacros_result->is_error_message()) {
+    std::move(callback).Run(
+        Result::NewErrorMessage(lacros_result->get_error_message()));
+  } else if (lacros_result->get_contents().empty()) {
+    std::move(callback).Run(
+        Result::NewAttribute(base::Optional<std::string>()));
+  } else {
+    std::move(callback).Run(
+        Result::NewAttribute(lacros_result->get_contents()));
+  }
+}
+#endif
+
+}  // namespace
+
+void GetDirectoryId(DeviceAPIService::GetDirectoryIdCallback callback) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const std::string attribute = g_browser_process->platform_part()
+                                    ->browser_policy_connector_chromeos()
+                                    ->GetDirectoryApiID();
+  if (attribute.empty())
+    std::move(callback).Run(
+        Result::NewAttribute(base::Optional<std::string>()));
+  else
+    std::move(callback).Run(Result::NewAttribute(attribute));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  chromeos::LacrosChromeServiceImpl::Get()
+      ->device_attributes_remote()
+      ->GetDirectoryDeviceId(
+          base::BindOnce(AdaptLacrosResult, std::move(callback)));
+#else  // Other platforms
+  std::move(callback).Run(
+      Result::NewErrorMessage(kNotSupportedPlatformErrorMessage));
+#endif
+}
+
+void GetSerialNumber(DeviceAPIService::GetSerialNumberCallback callback) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const std::string attribute =
+      chromeos::system::StatisticsProvider::GetInstance()
+          ->GetEnterpriseMachineID();
+  if (attribute.empty())
+    std::move(callback).Run(
+        Result::NewAttribute(base::Optional<std::string>()));
+  else
+    std::move(callback).Run(Result::NewAttribute(attribute));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  chromeos::LacrosChromeServiceImpl::Get()
+      ->device_attributes_remote()
+      ->GetDeviceSerialNumber(
+          base::BindOnce(AdaptLacrosResult, std::move(callback)));
+#else  // Other platforms
+  std::move(callback).Run(
+      Result::NewErrorMessage(kNotSupportedPlatformErrorMessage));
+#endif
+}
+
+void GetAnnotatedAssetId(
+    DeviceAPIService::GetAnnotatedAssetIdCallback callback) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const std::string attribute = g_browser_process->platform_part()
+                                    ->browser_policy_connector_chromeos()
+                                    ->GetDeviceAssetID();
+  if (attribute.empty())
+    std::move(callback).Run(
+        Result::NewAttribute(base::Optional<std::string>()));
+  else
+    std::move(callback).Run(Result::NewAttribute(attribute));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  chromeos::LacrosChromeServiceImpl::Get()
+      ->device_attributes_remote()
+      ->GetDeviceAssetId(
+          base::BindOnce(AdaptLacrosResult, std::move(callback)));
+#else  // Other platforms
+  std::move(callback).Run(
+      Result::NewErrorMessage(kNotSupportedPlatformErrorMessage));
+#endif
+}
+
+void GetAnnotatedLocation(
+    DeviceAPIService::GetAnnotatedLocationCallback callback) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const std::string attribute = g_browser_process->platform_part()
+                                    ->browser_policy_connector_chromeos()
+                                    ->GetDeviceAnnotatedLocation();
+  if (attribute.empty())
+    std::move(callback).Run(
+        Result::NewAttribute(base::Optional<std::string>()));
+  else
+    std::move(callback).Run(Result::NewAttribute(attribute));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  chromeos::LacrosChromeServiceImpl::Get()
+      ->device_attributes_remote()
+      ->GetDeviceAnnotatedLocation(
+          base::BindOnce(AdaptLacrosResult, std::move(callback)));
+#else  // Other platforms
+  std::move(callback).Run(
+      Result::NewErrorMessage(kNotSupportedPlatformErrorMessage));
+#endif
+}
+
+}  // namespace device_attribute_api
diff --git a/chrome/browser/device_api/device_attribute_api.h b/chrome/browser/device_api/device_attribute_api.h
new file mode 100644
index 0000000..698c2a0
--- /dev/null
+++ b/chrome/browser/device_api/device_attribute_api.h
@@ -0,0 +1,23 @@
+// 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 CHROME_BROWSER_DEVICE_API_DEVICE_ATTRIBUTE_API_H_
+#define CHROME_BROWSER_DEVICE_API_DEVICE_ATTRIBUTE_API_H_
+
+#include "third_party/blink/public/mojom/device/device.mojom.h"
+
+using blink::mojom::DeviceAPIService;
+
+namespace device_attribute_api {
+
+void GetDirectoryId(DeviceAPIService::GetDirectoryIdCallback callback);
+void GetSerialNumber(DeviceAPIService::GetSerialNumberCallback callback);
+void GetAnnotatedAssetId(
+    DeviceAPIService::GetAnnotatedAssetIdCallback callback);
+void GetAnnotatedLocation(
+    DeviceAPIService::GetAnnotatedLocationCallback callback);
+
+}  // namespace device_attribute_api
+
+#endif  // CHROME_BROWSER_DEVICE_API_DEVICE_ATTRIBUTE_API_H_
diff --git a/chrome/browser/device_api/device_attribute_api_browsertest.cc b/chrome/browser/device_api/device_attribute_api_browsertest.cc
new file mode 100644
index 0000000..db279e27
--- /dev/null
+++ b/chrome/browser/device_api/device_attribute_api_browsertest.cc
@@ -0,0 +1,107 @@
+// 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 "chrome/browser/device_api/device_attribute_api.h"
+
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
+#include "chromeos/system/fake_statistics_provider.h"
+#include "content/public/test/browser_test.h"
+
+namespace {
+
+constexpr char kAnnotatedAssetId[] = "annotated_asset_id";
+constexpr char kAnnotatedLocation[] = "annotated_location";
+constexpr char kDirectoryApiId[] = "directory_api_id";
+constexpr char kSerialNumber[] = "serial_number";
+
+}  // namespace
+
+// This test class provides unset device policy values and statistic data used
+// by device attributes APIs.
+class DeviceAttributeAPIUnsetTest : public policy::DevicePolicyCrosBrowserTest {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
+
+    // Init machine statistic.
+    fake_statistics_provider_.SetMachineStatistic(
+        chromeos::system::kSerialNumberKeyForTest, std::string());
+  }
+
+ private:
+  chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+};
+
+IN_PROC_BROWSER_TEST_F(DeviceAttributeAPIUnsetTest, AllAttributes) {
+  device_attribute_api::GetDirectoryId(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_FALSE(result->get_attribute().has_value());
+      }));
+
+  device_attribute_api::GetAnnotatedAssetId(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_FALSE(result->get_attribute().has_value());
+      }));
+
+  device_attribute_api::GetAnnotatedLocation(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_FALSE(result->get_attribute().has_value());
+      }));
+
+  device_attribute_api::GetSerialNumber(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_FALSE(result->get_attribute().has_value());
+      }));
+
+  base::RunLoop().RunUntilIdle();
+}
+
+// This test class provides regular device policy values and statistic data used
+// by device attributes APIs.
+class DeviceAttributeAPITest : public policy::DevicePolicyCrosBrowserTest {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    DevicePolicyCrosBrowserTest::SetUpInProcessBrowserTestFixture();
+
+    // Init the device policy.
+    device_policy()->SetDefaultSigningKey();
+    device_policy()->policy_data().set_annotated_asset_id(kAnnotatedAssetId);
+    device_policy()->policy_data().set_annotated_location(kAnnotatedLocation);
+    device_policy()->policy_data().set_directory_api_id(kDirectoryApiId);
+    device_policy()->Build();
+    RefreshDevicePolicy();
+
+    // Init machine statistic.
+    fake_statistics_provider_.SetMachineStatistic(
+        chromeos::system::kSerialNumberKeyForTest, kSerialNumber);
+  }
+
+ private:
+  chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+};
+
+IN_PROC_BROWSER_TEST_F(DeviceAttributeAPITest, AllAttributes) {
+  device_attribute_api::GetDirectoryId(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_EQ(result->get_attribute(), kDirectoryApiId);
+      }));
+
+  device_attribute_api::GetAnnotatedAssetId(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_EQ(result->get_attribute(), kAnnotatedAssetId);
+      }));
+
+  device_attribute_api::GetAnnotatedLocation(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_EQ(result->get_attribute(), kAnnotatedLocation);
+      }));
+
+  device_attribute_api::GetSerialNumber(
+      base::BindOnce([](blink::mojom::DeviceAttributeResultPtr result) {
+        EXPECT_EQ(result->get_attribute(), kSerialNumber);
+      }));
+
+  base::RunLoop().RunUntilIdle();
+}
diff --git a/chrome/browser/device_api/device_service_impl.cc b/chrome/browser/device_api/device_service_impl.cc
index ffab891..3a269749 100644
--- a/chrome/browser/device_api/device_service_impl.cc
+++ b/chrome/browser/device_api/device_service_impl.cc
@@ -5,9 +5,11 @@
 
 #include <memory>
 
+#include "chrome/browser/device_api/device_attribute_api.h"
 #include "chrome/browser/device_api/managed_configuration_api_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/policy/web_app_policy_constants.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
@@ -18,6 +20,11 @@
 
 bool IsTrustedContext(content::RenderFrameHost* host,
                       const url::Origin& origin) {
+  // TODO(anqing): This is used for dev trial. The flag will be removed when
+  // permission policies are ready.
+  if (!base::FeatureList::IsEnabled(features::kEnableRestrictedWebApis))
+    return false;
+
   PrefService* prefs =
       Profile::FromBrowserContext(host->GetBrowserContext())->GetPrefs();
 
@@ -46,6 +53,7 @@
                           base::Unretained(this)));
   managed_configuration_api()->AddObserver(origin(), this);
 }
+
 DeviceServiceImpl::~DeviceServiceImpl() {
   managed_configuration_api()->RemoveObserver(origin(), this);
 }
@@ -105,3 +113,21 @@
 void DeviceServiceImpl::OnManagedConfigurationChanged() {
   configuration_subscription_->OnConfigurationChanged();
 }
+
+void DeviceServiceImpl::GetDirectoryId(GetDirectoryIdCallback callback) {
+  device_attribute_api::GetDirectoryId(std::move(callback));
+}
+
+void DeviceServiceImpl::GetSerialNumber(GetSerialNumberCallback callback) {
+  device_attribute_api::GetSerialNumber(std::move(callback));
+}
+
+void DeviceServiceImpl::GetAnnotatedAssetId(
+    GetAnnotatedAssetIdCallback callback) {
+  device_attribute_api::GetAnnotatedAssetId(std::move(callback));
+}
+
+void DeviceServiceImpl::GetAnnotatedLocation(
+    GetAnnotatedLocationCallback callback) {
+  device_attribute_api::GetAnnotatedLocation(std::move(callback));
+}
diff --git a/chrome/browser/device_api/device_service_impl.h b/chrome/browser/device_api/device_service_impl.h
index 0831e5d..eef44277 100644
--- a/chrome/browser/device_api/device_service_impl.h
+++ b/chrome/browser/device_api/device_service_impl.h
@@ -38,6 +38,10 @@
   void SubscribeToManagedConfiguration(
       mojo::PendingRemote<blink::mojom::ManagedConfigurationObserver> observer)
       override;
+  void GetDirectoryId(GetDirectoryIdCallback callback) override;
+  void GetSerialNumber(GetSerialNumberCallback callback) override;
+  void GetAnnotatedAssetId(GetAnnotatedAssetIdCallback callback) override;
+  void GetAnnotatedLocation(GetAnnotatedLocationCallback callback) override;
 
  private:
   DeviceServiceImpl(
@@ -47,6 +51,7 @@
   void OnForceInstallWebAppListChanged();
 
   ManagedConfigurationAPI* managed_configuration_api();
+
   // ManagedConfigurationAPI::Observer:
   void OnManagedConfigurationChanged() override;
 
diff --git a/chrome/browser/device_api/device_service_unittest.cc b/chrome/browser/device_api/device_service_unittest.cc
index 2b7f547..7184e98 100644
--- a/chrome/browser/device_api/device_service_unittest.cc
+++ b/chrome/browser/device_api/device_service_unittest.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "chrome/browser/web_applications/components/policy/web_app_policy_constants.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
@@ -54,25 +55,56 @@
 
  private:
   mojo::Remote<blink::mojom::DeviceAPIService> remote_;
-  std::unique_ptr<base::RunLoop> loop_;
 };
 
-TEST_F(DeviceAPIServiceTest, ConnectsForTrustedApps) {
+TEST_F(DeviceAPIServiceTest, FlagOffByDefault) {
+  TryCreatingService(GURL(kTrustedUrl));
+  remote()->FlushForTesting();
+  ASSERT_FALSE(remote()->is_connected());
+}
+
+class DeviceAPIServiceWithFeatureFlagTest : public DeviceAPIServiceTest {
+ public:
+  DeviceAPIServiceWithFeatureFlagTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kEnableRestrictedWebApis);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(DeviceAPIServiceWithFeatureFlagTest, ConnectsForTrustedApps) {
   TryCreatingService(GURL(kTrustedUrl));
   remote()->FlushForTesting();
   ASSERT_TRUE(remote()->is_connected());
 }
 
-TEST_F(DeviceAPIServiceTest, DoesNotConnectForUntrustedApps) {
+TEST_F(DeviceAPIServiceWithFeatureFlagTest, DoesNotConnectForUntrustedApps) {
   TryCreatingService(GURL(kUntrustedUrl));
   remote()->FlushForTesting();
   ASSERT_FALSE(remote()->is_connected());
 }
 
-TEST_F(DeviceAPIServiceTest, DisconnectWhenTrustRevoked) {
+TEST_F(DeviceAPIServiceWithFeatureFlagTest, DisconnectWhenTrustRevoked) {
   TryCreatingService(GURL(kTrustedUrl));
   remote()->FlushForTesting();
   RemoveTrustedApp();
   remote()->FlushForTesting();
   ASSERT_FALSE(remote()->is_connected());
 }
+
+class DeviceAPIServiceWithoutFeatureFlagTest : public DeviceAPIServiceTest {
+ public:
+  DeviceAPIServiceWithoutFeatureFlagTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kEnableRestrictedWebApis);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(DeviceAPIServiceWithoutFeatureFlagTest, DoesNotConnectWhenFlagOff) {
+  TryCreatingService(GURL(kTrustedUrl));
+  remote()->FlushForTesting();
+  ASSERT_FALSE(remote()->is_connected());
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 22e9abe1..ae83db30 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2173,6 +2173,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "enable-restricted-web-apis",
+    "owners": [ "anqing", "apotapchuk" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "enable-save-data",
     "owners": [ "//components/data_reduction_proxy/OWNERS" ],
     // This flag is used for frequent manual testing and should not be removed.
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 1786e59..4028125c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -973,6 +973,12 @@
     "Predicts the scroll amount after the vsync time to more closely match "
     "when the frame is visible.";
 
+extern const char kEnableRestrictedWebApisName[] =
+    "Enable the restriced web APIs for high-trusted apps.";
+extern const char kEnableRestrictedWebApisDescription[] =
+    "Enable the restricted web APIs for dev trial. This will be replaced with "
+    "permission policies to control the capabilities afterwards.";
+
 const char kEnableTabSearchFlagId[] = "enable-tab-search";
 const char kEnableTabSearchName[] = "Enable Tab Search";
 const char kEnableTabSearchDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 7412553..776a7a66 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -575,6 +575,9 @@
 extern const char
     kEnableResamplingScrollEventsExperimentalPredictionDescription[];
 
+extern const char kEnableRestrictedWebApisName[];
+extern const char kEnableRestrictedWebApisDescription[];
+
 extern const char kEnableSubresourceRedirectName[];
 extern const char kEnableSubresourceRedirectDescription[];
 
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 0703873..38a9221 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -364,6 +364,10 @@
     "EnableIncognitoShortcutOnDesktop", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
+// Enable the restricted web APIs for high-trusted apps.
+const base::Feature kEnableRestrictedWebApis{"EnableRestrictedWebApis",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable web app uninstallation from Windows settings or control panel.
 const base::Feature kEnableWebAppUninstallFromOsSettings{
     "EnableWebAppUninstallFromOsSettings", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index c7781dd..2341e49 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -243,6 +243,9 @@
 #endif
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kEnableRestrictedWebApis;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kEnableWebAppUninstallFromOsSettings;
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index dc2d078..6cd38cc7 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2752,6 +2752,7 @@
         "../browser/chromeos/web_applications/system_web_app_integration_test.cc",
         "../browser/chromeos/web_applications/system_web_app_integration_test.h",
         "../browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service_browsertest.cc",
+        "../browser/device_api/device_attribute_api_browsertest.cc",
         "../browser/download/notification/download_notification_browsertest.cc",
         "../browser/drive/drive_notification_manager_factory_browsertest.cc",
         "../browser/extensions/api/certificate_provider/certificate_provider_apitest.cc",
diff --git a/third_party/blink/public/mojom/device/device.mojom b/third_party/blink/public/mojom/device/device.mojom
index ad9aee17cb..fee19e60 100644
--- a/third_party/blink/public/mojom/device/device.mojom
+++ b/third_party/blink/public/mojom/device/device.mojom
@@ -26,5 +26,34 @@
   SubscribeToManagedConfiguration(
     pending_remote<ManagedConfigurationObserver> observer);
 
+  // Fetches the value of the device identifier of the directory API, that is
+  // generated by the server and identifies the cloud record of the device for
+  // querying in the cloud directory API. If the current user is not affiliated,
+  // returns nullopt as |attribute|.
+  GetDirectoryId() => (DeviceAttributeResult result);
+
+  // Fetches the device's serial number. Please note the purpose of this API is
+  // to administrate the device (e.g. generating Certificate Sign Requests for
+  // device-wide certificates). If the current user is not affiliated, returns nullopt
+  // as |attribute|.
+  GetSerialNumber() => (DeviceAttributeResult result);
+
+  // Fetches the administrator-annotated Asset Id. If the current user is not
+  // affiliated or no Asset Id has been set by the administrator, returns nullopt
+  // as |attribute|.
+  GetAnnotatedAssetId() => (DeviceAttributeResult result);
+
+  // Fetches the administrator-annotated location. If the current user is not
+  // affiliated or no Annotated Location has been set by the administrator,
+  // returns nullopt as |attribute|.
+  GetAnnotatedLocation() => (DeviceAttributeResult result);
 };
 
+// Returned by methods that either return a nullable string or an error.
+union DeviceAttributeResult {
+  // Implies failure.
+  string error_message;
+
+  // Implies success.
+  string? attribute;
+};
diff --git a/third_party/blink/renderer/modules/device/device_service.cc b/third_party/blink/renderer/modules/device/device_service.cc
index b372cdc..726c1be 100644
--- a/third_party/blink/renderer/modules/device/device_service.cc
+++ b/third_party/blink/renderer/modules/device/device_service.cc
@@ -17,9 +17,9 @@
 
 namespace {
 
-const DOMExceptionCode kDOMExceptionCode = DOMExceptionCode::kNotAllowedError;
-const char kDOMExceptionMessage[] =
+const char kNotHighTrustedAppExceptionMessage[] =
     "This API is available only for high trusted apps.";
+
 }  // namespace
 
 const char DeviceService::kSupplementName[] = "DeviceService";
@@ -83,9 +83,11 @@
 void DeviceService::OnServiceConnectionError() {
   device_api_service_.reset();
   // Resolve all pending promises with a failure.
-  for (ScriptPromiseResolver* resolver : pending_promises_)
-    resolver->Reject(MakeGarbageCollected<DOMException>(kDOMExceptionCode,
-                                                        kDOMExceptionMessage));
+  for (ScriptPromiseResolver* resolver : pending_promises_) {
+    resolver->Reject(
+        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError,
+                                           kNotHighTrustedAppExceptionMessage));
+  }
 }
 
 ScriptPromise DeviceService::getManagedConfiguration(ScriptState* script_state,
@@ -100,6 +102,50 @@
   return promise;
 }
 
+ScriptPromise DeviceService::getDirectoryId(ScriptState* script_state) {
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  pending_promises_.insert(resolver);
+
+  ScriptPromise promise = resolver->Promise();
+  GetService()->GetDirectoryId(
+      WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
+                WrapPersistent(script_state), WrapPersistent(resolver)));
+  return promise;
+}
+
+ScriptPromise DeviceService::getSerialNumber(ScriptState* script_state) {
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  pending_promises_.insert(resolver);
+
+  ScriptPromise promise = resolver->Promise();
+  GetService()->GetSerialNumber(
+      WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
+                WrapPersistent(script_state), WrapPersistent(resolver)));
+  return promise;
+}
+
+ScriptPromise DeviceService::getAnnotatedAssetId(ScriptState* script_state) {
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  pending_promises_.insert(resolver);
+
+  ScriptPromise promise = resolver->Promise();
+  GetService()->GetAnnotatedAssetId(
+      WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
+                WrapPersistent(script_state), WrapPersistent(resolver)));
+  return promise;
+}
+
+ScriptPromise DeviceService::getAnnotatedLocation(ScriptState* script_state) {
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  pending_promises_.insert(resolver);
+
+  ScriptPromise promise = resolver->Promise();
+  GetService()->GetAnnotatedLocation(
+      WTF::Bind(&DeviceService::OnAttributeReceived, WrapWeakPersistent(this),
+                WrapPersistent(script_state), WrapPersistent(resolver)));
+  return promise;
+}
+
 void DeviceService::OnConfigurationReceived(
     ScriptPromiseResolver* scoped_resolver,
     const HashMap<String, String>& configurations) {
@@ -120,6 +166,22 @@
   scoped_resolver->Resolve(result.GetScriptValue());
 }
 
+void DeviceService::OnAttributeReceived(
+    ScriptState* script_state,
+    ScriptPromiseResolver* scoped_resolver,
+    mojom::blink::DeviceAttributeResultPtr result) {
+  pending_promises_.erase(scoped_resolver);
+
+  if (result->is_error_message()) {
+    scoped_resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kUnknownError, result->get_error_message()));
+  } else if (result->get_attribute().IsNull()) {
+    scoped_resolver->Resolve(v8::Null(script_state->GetIsolate()));
+  } else {
+    scoped_resolver->Resolve(result->get_attribute());
+  }
+}
+
 void DeviceService::OnConfigurationChanged() {
   DispatchEvent(*Event::Create(event_type_names::kManagedconfigurationchange));
 }
diff --git a/third_party/blink/renderer/modules/device/device_service.h b/third_party/blink/renderer/modules/device/device_service.h
index 30213ed..15879f3 100644
--- a/third_party/blink/renderer/modules/device/device_service.h
+++ b/third_party/blink/renderer/modules/device/device_service.h
@@ -60,12 +60,23 @@
   DEFINE_ATTRIBUTE_EVENT_LISTENER(managedconfigurationchange,
                                   kManagedconfigurationchange)
 
+  // Device Attributes API:
+  ScriptPromise getDirectoryId(ScriptState* script_state);
+  ScriptPromise getSerialNumber(ScriptState* script_state);
+  ScriptPromise getAnnotatedAssetId(ScriptState* script_state);
+  ScriptPromise getAnnotatedLocation(ScriptState* script_state);
+
  private:
   // ManagedConfigurationObserver:
   void OnConfigurationChanged() override;
 
   void OnConfigurationReceived(ScriptPromiseResolver* scoped_resolver,
                                const HashMap<String, String>& configurations);
+
+  void OnAttributeReceived(ScriptState* script_state,
+                           ScriptPromiseResolver* scoped_resolver,
+                           mojom::blink::DeviceAttributeResultPtr result);
+
   // Lazily binds mojo interface.
   mojom::blink::DeviceAPIService* GetService();
 
diff --git a/third_party/blink/renderer/modules/device/device_service.idl b/third_party/blink/renderer/modules/device/device_service.idl
index c555535..6473e224 100644
--- a/third_party/blink/renderer/modules/device/device_service.idl
+++ b/third_party/blink/renderer/modules/device/device_service.idl
@@ -12,4 +12,10 @@
   [CallWith=ScriptState]
   Promise<object> getManagedConfiguration(sequence<DOMString> keys);
   attribute EventHandler onmanagedconfigurationchange;
+
+  // Device Attributes API.
+  [CallWith=ScriptState] Promise<DOMString> getDirectoryId();
+  [CallWith=ScriptState] Promise<DOMString> getSerialNumber();
+  [CallWith=ScriptState] Promise<DOMString> getAnnotatedAssetId();
+  [CallWith=ScriptState] Promise<DOMString> getAnnotatedLocation();
 };
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 478a514..ba63608 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -1584,7 +1584,11 @@
     attribute @@toStringTag
     getter onmanagedconfigurationchange
     method constructor
+    method getAnnotatedAssetId
+    method getAnnotatedLocation
+    method getDirectoryId
     method getManagedConfiguration
+    method getSerialNumber
     setter onmanagedconfigurationchange
 interface Document : Node
     attribute @@toStringTag
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b6757d2..00dc73b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -46224,6 +46224,7 @@
       label="PrefetchMainResourceNetworkIsolationKey:disabled"/>
   <int value="1411679884"
       label="AutofillLocalCardMigrationUsesStrikeSystemV2:enabled"/>
+  <int value="1412874669" label="EnableRestrictedWebApis:enabled"/>
   <int value="1413158119" label="WebRtcRemoteEventLog:disabled"/>
   <int value="1413334779" label="WebPaymentsMinimalUI:enabled"/>
   <int value="1413948819" label="NupPrinting:enabled"/>
@@ -46986,6 +46987,7 @@
   <int value="2137599770" label="enable-win32k-renderer-lockdown"/>
   <int value="2138146331" label="OmniboxVoiceSearchAlwaysVisible:enabled"/>
   <int value="2139048614" label="UseSurfaceLayerForVideo:enabled"/>
+  <int value="2139520082" label="EnableRestrictedWebApis:disabled"/>
   <int value="2140453427"
       label="MigrateDefaultChromeAppToWebAppsGSuite:enabled"/>
   <int value="2141067485" label="SystemWebApps:enabled"/>