Browse Source

feat(extensions): add support for some chrome.management APIs (#25344)

* feat(extensions): add support for some chrome.management APIs (#25098)

* fix: initialize management policy

* fix(extensions): crash when using chrome.management

* test: add tests

* docs: add a note about chrome.management

* fix: lint errors

* fix: lint errors

* fix: remove favicon_service include

* fix: add missing management permission

* docs: more supported apis

* fix: extensions.md line endings

* feat(extensions): add support for some chrome.management APIs (#25098)

* fix: initialize management policy

* fix(extensions): crash when using chrome.management

* test: add tests

* docs: add a note about chrome.management

* fix: lint errors

* fix: lint errors

* fix: remove favicon_service include

* fix: add missing management permission

* docs: more supported apis

* fix: extensions.md line endings

* Update electron_extensions_api_client.cc
Eryk Rakowski 4 years ago
parent
commit
6e734570ac

+ 15 - 0
docs/api/extensions.md

@@ -28,6 +28,9 @@ session.loadExtension('path/to/unpacked/extension').then(({ id }) => {
 Loaded extensions will not be automatically remembered across exits; if you do
 not call `loadExtension` when the app runs, the extension will not be loaded.
 
+Note that loading extensions is only supported in persistent sessions.
+Attempting to load an extension into an in-memory session will throw an error.
+
 See the [`session`](session.md) documentation for more information about
 loading, unloading, and querying active extensions.
 
@@ -99,3 +102,15 @@ The following methods of `chrome.tabs` are supported:
 > **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active
 > tab". Since Electron has no such concept, passing `-1` as a tab ID is not
 > supported and will raise an error.
+
+### `chrome.management`
+
+The following methods of `chrome.management` are supported:
+
+- `chrome.management.getAll`
+- `chrome.management.get`
+- `chrome.management.getSelf`
+- `chrome.management.getPermissionWarningsById`
+- `chrome.management.getPermissionWarningsByManifest`
+- `chrome.management.onEnabled`
+- `chrome.management.onDisabled`

+ 2 - 0
filenames.gni

@@ -614,6 +614,8 @@ filenames = {
     "shell/browser/extensions/api/resources_private/resources_private_api.h",
     "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
     "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
+    "shell/browser/extensions/api/management/electron_management_api_delegate.cc",
+    "shell/browser/extensions/api/management/electron_management_api_delegate.h",
     "shell/browser/extensions/api/tabs/tabs_api.cc",
     "shell/browser/extensions/api/tabs/tabs_api.h",
     "shell/browser/extensions/api/streams_private/streams_private_api.cc",

+ 235 - 0
shell/browser/extensions/api/management/electron_management_api_delegate.cc

@@ -0,0 +1,235 @@
+// Copyright 2014 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.
+
+// TODO(sentialx): emit relevant events in Electron's session?
+#include "shell/browser/extensions/api/management/electron_management_api_delegate.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "chrome/common/extensions/extension_metrics.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/web_application_info.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/api/management/management_api.h"
+#include "extensions/browser/api/management/management_api_constants.h"
+#include "extensions/browser/disable_reason.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/api/management.h"
+#include "extensions/common/extension.h"
+#include "services/data_decoder/public/cpp/data_decoder.h"
+#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+
+namespace {
+class ManagementSetEnabledFunctionInstallPromptDelegate
+    : public extensions::InstallPromptDelegate {
+ public:
+  ManagementSetEnabledFunctionInstallPromptDelegate(
+      content::WebContents* web_contents,
+      content::BrowserContext* browser_context,
+      const extensions::Extension* extension,
+      const base::Callback<void(bool)>& callback) {
+    // TODO(sentialx): emit event
+  }
+  ~ManagementSetEnabledFunctionInstallPromptDelegate() override {}
+
+ private:
+  base::WeakPtrFactory<ManagementSetEnabledFunctionInstallPromptDelegate>
+      weak_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(ManagementSetEnabledFunctionInstallPromptDelegate);
+};
+
+class ManagementUninstallFunctionUninstallDialogDelegate
+    : public extensions::UninstallDialogDelegate {
+ public:
+  ManagementUninstallFunctionUninstallDialogDelegate(
+      extensions::ManagementUninstallFunctionBase* function,
+      const extensions::Extension* target_extension,
+      bool show_programmatic_uninstall_ui) {
+    // TODO(sentialx): emit event
+  }
+
+  ~ManagementUninstallFunctionUninstallDialogDelegate() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ManagementUninstallFunctionUninstallDialogDelegate);
+};
+
+}  // namespace
+
+ElectronManagementAPIDelegate::ElectronManagementAPIDelegate() {}
+
+ElectronManagementAPIDelegate::~ElectronManagementAPIDelegate() {}
+
+void ElectronManagementAPIDelegate::LaunchAppFunctionDelegate(
+    const extensions::Extension* extension,
+    content::BrowserContext* context) const {
+  // TODO(sentialx): emit event
+  extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_EXTENSION_API,
+                                  extension->GetType());
+}
+
+GURL ElectronManagementAPIDelegate::GetFullLaunchURL(
+    const extensions::Extension* extension) const {
+  return extensions::AppLaunchInfo::GetFullLaunchURL(extension);
+}
+
+extensions::LaunchType ElectronManagementAPIDelegate::GetLaunchType(
+    const extensions::ExtensionPrefs* prefs,
+    const extensions::Extension* extension) const {
+  // TODO(sentialx)
+  return extensions::LAUNCH_TYPE_DEFAULT;
+}
+
+void ElectronManagementAPIDelegate::
+    GetPermissionWarningsByManifestFunctionDelegate(
+        extensions::ManagementGetPermissionWarningsByManifestFunction* function,
+        const std::string& manifest_str) const {
+  data_decoder::DataDecoder::ParseJsonIsolated(
+      manifest_str,
+      base::BindOnce(
+          &extensions::ManagementGetPermissionWarningsByManifestFunction::
+              OnParse,
+          function));
+}
+
+std::unique_ptr<extensions::InstallPromptDelegate>
+ElectronManagementAPIDelegate::SetEnabledFunctionDelegate(
+    content::WebContents* web_contents,
+    content::BrowserContext* browser_context,
+    const extensions::Extension* extension,
+    const base::Callback<void(bool)>& callback) const {
+  return std::unique_ptr<ManagementSetEnabledFunctionInstallPromptDelegate>(
+      new ManagementSetEnabledFunctionInstallPromptDelegate(
+          web_contents, browser_context, extension, callback));
+}
+
+std::unique_ptr<extensions::UninstallDialogDelegate>
+ElectronManagementAPIDelegate::UninstallFunctionDelegate(
+    extensions::ManagementUninstallFunctionBase* function,
+    const extensions::Extension* target_extension,
+    bool show_programmatic_uninstall_ui) const {
+  return std::unique_ptr<extensions::UninstallDialogDelegate>(
+      new ManagementUninstallFunctionUninstallDialogDelegate(
+          function, target_extension, show_programmatic_uninstall_ui));
+}
+
+bool ElectronManagementAPIDelegate::CreateAppShortcutFunctionDelegate(
+    extensions::ManagementCreateAppShortcutFunction* function,
+    const extensions::Extension* extension,
+    std::string* error) const {
+  return false;  // TODO(sentialx): route event and return true
+}
+
+std::unique_ptr<extensions::AppForLinkDelegate>
+ElectronManagementAPIDelegate::GenerateAppForLinkFunctionDelegate(
+    extensions::ManagementGenerateAppForLinkFunction* function,
+    content::BrowserContext* context,
+    const std::string& title,
+    const GURL& launch_url) const {
+  // TODO(sentialx)
+  return nullptr;
+}
+
+bool ElectronManagementAPIDelegate::CanContextInstallWebApps(
+    content::BrowserContext* context) const {
+  // TODO(sentialx)
+  return false;
+}
+
+void ElectronManagementAPIDelegate::InstallOrLaunchReplacementWebApp(
+    content::BrowserContext* context,
+    const GURL& web_app_url,
+    InstallOrLaunchWebAppCallback callback) const {
+  // TODO(sentialx)
+}
+
+bool ElectronManagementAPIDelegate::CanContextInstallAndroidApps(
+    content::BrowserContext* context) const {
+  return false;
+}
+
+void ElectronManagementAPIDelegate::CheckAndroidAppInstallStatus(
+    const std::string& package_name,
+    AndroidAppInstallStatusCallback callback) const {
+  std::move(callback).Run(false);
+}
+
+void ElectronManagementAPIDelegate::InstallReplacementAndroidApp(
+    const std::string& package_name,
+    InstallAndroidAppCallback callback) const {
+  std::move(callback).Run(false);
+}
+
+void ElectronManagementAPIDelegate::EnableExtension(
+    content::BrowserContext* context,
+    const std::string& extension_id) const {
+  // const extensions::Extension* extension =
+  //     extensions::ExtensionRegistry::Get(context)->GetExtensionById(
+  //         extension_id, extensions::ExtensionRegistry::EVERYTHING);
+
+  // TODO(sentialx): we don't have ExtensionService
+  // If the extension was disabled for a permissions increase, the Management
+  // API will have displayed a re-enable prompt to the user, so we know it's
+  // safe to grant permissions here.
+  // extensions::ExtensionSystem::Get(context)
+  //     ->extension_service()
+  //     ->GrantPermissionsAndEnableExtension(extension);
+}
+
+void ElectronManagementAPIDelegate::DisableExtension(
+    content::BrowserContext* context,
+    const extensions::Extension* source_extension,
+    const std::string& extension_id,
+    extensions::disable_reason::DisableReason disable_reason) const {
+  // TODO(sentialx): we don't have ExtensionService
+  // extensions::ExtensionSystem::Get(context)
+  //     ->extension_service()
+  //     ->DisableExtensionWithSource(source_extension, extension_id,
+  //                                  disable_reason);
+}
+
+bool ElectronManagementAPIDelegate::UninstallExtension(
+    content::BrowserContext* context,
+    const std::string& transient_extension_id,
+    extensions::UninstallReason reason,
+    base::string16* error) const {
+  // TODO(sentialx): we don't have ExtensionService
+  // return extensions::ExtensionSystem::Get(context)
+  //     ->extension_service()
+  //     ->UninstallExtension(transient_extension_id, reason, error);
+  return false;
+}
+
+void ElectronManagementAPIDelegate::SetLaunchType(
+    content::BrowserContext* context,
+    const std::string& extension_id,
+    extensions::LaunchType launch_type) const {
+  // TODO(sentialx)
+  // extensions::SetLaunchType(context, extension_id, launch_type);
+}
+
+GURL ElectronManagementAPIDelegate::GetIconURL(
+    const extensions::Extension* extension,
+    int icon_size,
+    ExtensionIconSet::MatchType match,
+    bool grayscale) const {
+  GURL icon_url(base::StringPrintf("%s%s/%d/%d%s",
+                                   chrome::kChromeUIExtensionIconURL,
+                                   extension->id().c_str(), icon_size, match,
+                                   grayscale ? "?grayscale=true" : ""));
+  CHECK(icon_url.is_valid());
+  return icon_url;
+}

+ 86 - 0
shell/browser/extensions/api/management/electron_management_api_delegate.h

@@ -0,0 +1,86 @@
+// Copyright 2014 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 SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_
+#define SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/task/cancelable_task_tracker.h"
+#include "extensions/browser/api/management/management_api_delegate.h"
+
+class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate {
+ public:
+  ElectronManagementAPIDelegate();
+  ~ElectronManagementAPIDelegate() override;
+
+  // ManagementAPIDelegate.
+  void LaunchAppFunctionDelegate(
+      const extensions::Extension* extension,
+      content::BrowserContext* context) const override;
+  GURL GetFullLaunchURL(const extensions::Extension* extension) const override;
+  extensions::LaunchType GetLaunchType(
+      const extensions::ExtensionPrefs* prefs,
+      const extensions::Extension* extension) const override;
+  void GetPermissionWarningsByManifestFunctionDelegate(
+      extensions::ManagementGetPermissionWarningsByManifestFunction* function,
+      const std::string& manifest_str) const override;
+  std::unique_ptr<extensions::InstallPromptDelegate> SetEnabledFunctionDelegate(
+      content::WebContents* web_contents,
+      content::BrowserContext* browser_context,
+      const extensions::Extension* extension,
+      const base::Callback<void(bool)>& callback) const override;
+  std::unique_ptr<extensions::UninstallDialogDelegate>
+  UninstallFunctionDelegate(
+      extensions::ManagementUninstallFunctionBase* function,
+      const extensions::Extension* target_extension,
+      bool show_programmatic_uninstall_ui) const override;
+  bool CreateAppShortcutFunctionDelegate(
+      extensions::ManagementCreateAppShortcutFunction* function,
+      const extensions::Extension* extension,
+      std::string* error) const override;
+  std::unique_ptr<extensions::AppForLinkDelegate>
+  GenerateAppForLinkFunctionDelegate(
+      extensions::ManagementGenerateAppForLinkFunction* function,
+      content::BrowserContext* context,
+      const std::string& title,
+      const GURL& launch_url) const override;
+  bool CanContextInstallWebApps(
+      content::BrowserContext* context) const override;
+  void InstallOrLaunchReplacementWebApp(
+      content::BrowserContext* context,
+      const GURL& web_app_url,
+      ManagementAPIDelegate::InstallOrLaunchWebAppCallback callback)
+      const override;
+  bool CanContextInstallAndroidApps(
+      content::BrowserContext* context) const override;
+  void CheckAndroidAppInstallStatus(
+      const std::string& package_name,
+      ManagementAPIDelegate::AndroidAppInstallStatusCallback callback)
+      const override;
+  void InstallReplacementAndroidApp(
+      const std::string& package_name,
+      ManagementAPIDelegate::InstallAndroidAppCallback callback) const override;
+  void EnableExtension(content::BrowserContext* context,
+                       const std::string& extension_id) const override;
+  void DisableExtension(
+      content::BrowserContext* context,
+      const extensions::Extension* source_extension,
+      const std::string& extension_id,
+      extensions::disable_reason::DisableReason disable_reason) const override;
+  bool UninstallExtension(content::BrowserContext* context,
+                          const std::string& transient_extension_id,
+                          extensions::UninstallReason reason,
+                          base::string16* error) const override;
+  void SetLaunchType(content::BrowserContext* context,
+                     const std::string& extension_id,
+                     extensions::LaunchType launch_type) const override;
+  GURL GetIconURL(const extensions::Extension* extension,
+                  int icon_size,
+                  ExtensionIconSet::MatchType match,
+                  bool grayscale) const override;
+};
+
+#endif  // SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_

+ 4 - 1
shell/browser/extensions/electron_extension_system.cc

@@ -25,6 +25,7 @@
 #include "extensions/browser/api/app_runtime/app_runtime_api.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/info_map.h"
+#include "extensions/browser/management_policy.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/browser/null_app_sorting.h"
 #include "extensions/browser/quota_service.h"
@@ -95,6 +96,8 @@ void ElectronExtensionSystem::InitForRegularProfile(bool extensions_enabled) {
 
   if (!browser_context_->IsOffTheRecord())
     LoadComponentExtensions();
+
+  management_policy_.reset(new ManagementPolicy);
 }
 
 std::unique_ptr<base::DictionaryValue> ParseManifest(
@@ -134,7 +137,7 @@ RuntimeData* ElectronExtensionSystem::runtime_data() {
 }
 
 ManagementPolicy* ElectronExtensionSystem::management_policy() {
-  return nullptr;
+  return management_policy_.get();
 }
 
 ServiceWorkerManager* ElectronExtensionSystem::service_worker_manager() {

+ 1 - 0
shell/browser/extensions/electron_extension_system.h

@@ -100,6 +100,7 @@ class ElectronExtensionSystem : public ExtensionSystem {
   std::unique_ptr<QuotaService> quota_service_;
   std::unique_ptr<SharedUserScriptMaster> shared_user_script_master_;
   std::unique_ptr<AppSorting> app_sorting_;
+  std::unique_ptr<ManagementPolicy> management_policy_;
 
   std::unique_ptr<ElectronExtensionLoader> extension_loader_;
 

+ 6 - 0
shell/browser/extensions/electron_extensions_api_client.cc

@@ -9,6 +9,7 @@
 
 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h"
 #include "printing/buildflags/buildflags.h"
+#include "shell/browser/extensions/api/management/electron_management_api_delegate.h"
 #include "shell/browser/extensions/electron_extension_web_contents_observer.h"
 #include "shell/browser/extensions/electron_messaging_delegate.h"
 
@@ -59,6 +60,11 @@ void ElectronExtensionsAPIClient::AttachWebContentsHelpers(
 #endif
 }
 
+ManagementAPIDelegate*
+ElectronExtensionsAPIClient::CreateManagementAPIDelegate() const {
+  return new ElectronManagementAPIDelegate;
+}
+
 std::unique_ptr<MimeHandlerViewGuestDelegate>
 ElectronExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate(
     MimeHandlerViewGuest* guest) const {

+ 1 - 0
shell/browser/extensions/electron_extensions_api_client.h

@@ -25,6 +25,7 @@ class ElectronExtensionsAPIClient : public ExtensionsAPIClient {
   std::unique_ptr<MimeHandlerViewGuestDelegate>
   CreateMimeHandlerViewGuestDelegate(
       MimeHandlerViewGuest* guest) const override;
+  ManagementAPIDelegate* CreateManagementAPIDelegate() const override;
 
  private:
   std::unique_ptr<ElectronMessagingDelegate> messaging_delegate_;

+ 62 - 2
shell/common/extensions/api/_permission_features.json

@@ -5,5 +5,65 @@
       "extension"
     ],
     "location": "component"
-  }
-}
+  },
+  "management": [
+    {
+      "channel": "stable",
+      "extension_types": [
+        "extension",
+        "legacy_packaged_app"
+      ]
+    },
+    {
+      "channel": "stable",
+      "extension_types": [
+        "platform_app"
+      ],
+      "whitelist": [
+        "AE27D69DBE571F4B1694F05C89B710C646792231", // Published ADT
+        // TODO(grv): clean up once Apps developer tool is published.
+        "5107DE9024C329EEA9C9A72D94C16723790C6422", // Apps Developer Tool.
+        "8C0B1873FFFB65E4D0F4D772879F7304CEF125C2", // Apps Editor old.
+        "FA0501B579070BB9CBD4FCAEC8CB0EDF22BA2F04", // Apps Editor published.
+        "EE17C698905F7F2E6DDC87C9C30F11E164C829F4", // Watchdog (Activity Log)
+        "90113DA9516526D24DAF156C629CC41C049E8882", // Watchdog Test Version
+        "4A4EA121622FCA3D78ED2AB534197F43D7189EE0", // Spark nightly build.
+        "9FDE6E7F06FCFA11D9A05041C7FF6D8AE662F5D1", // Spark release.
+        "50B4A905D522C06E27CA6D099E3E54BDA1F152C5", // Spark Beta channel.
+        "BA0C8BB92084C9741312D90D3EA882526853455F", // Spark dev channel.
+        "5F57A9AE8DFF5D6BB09DF8606270402612E871E5", // http://crbug.com/422624
+        "46578A13607D38F1DC8E280C4F499FB0A2F9565C", // http://crbug.com/819404
+        "898FB5A39687D210766B8998BA4530B99C9E6586", // http://crbug.com/819404
+        "82F30B65397BC3E4ADE627BBD857AB8A58210648", // http://crbug.com/819404
+        "C74B2AF138F9EDECD04D0965AB36CA66C8290466" // http://crbug.com/957772
+      ]
+    },
+    {
+      "channel": "stable",
+      "extension_types": [
+        "hosted_app"
+      ],
+      "whitelist": [
+        "B44D08FD98F1523ED5837D78D0A606EA9D6206E5" // Web Store
+      ]
+    },
+    {
+      "channel": "stable",
+      "extension_types": [
+        "platform_app"
+      ],
+      "session_types": [
+        "kiosk"
+      ]
+    },
+    {
+      "channel": "stable",
+      "dependencies": [
+        "behavior:imprivata_login_screen_extension"
+      ],
+      "extension_types": [
+        "login_screen_extension"
+      ]
+    }
+  ]
+}

+ 1 - 0
shell/common/extensions/electron_extensions_api_provider.cc

@@ -38,6 +38,7 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = {
          APIPermissionInfo::kFlagInternal},
     {APIPermission::kResourcesPrivate, "resourcesPrivate",
      APIPermissionInfo::kFlagCannotBeOptional},
+    {APIPermission::kManagement, "management"},
 };
 base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
   return base::make_span(permissions_to_register);

+ 20 - 0
spec-main/extensions-spec.ts

@@ -36,6 +36,26 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex
     });
   });
 
+  it('does not crash when using chrome.management', async () => {
+    const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
+    const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });
+    w.loadURL('about:blank');
+
+    await emittedOnce(w.webContents, 'dom-ready');
+    await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
+    const args: any = await emittedOnce(app, 'web-contents-created');
+    const wc: Electron.WebContents = args[1];
+    await expect(wc.executeJavaScript(`
+      (() => {
+        return new Promise((resolve) => {
+          chrome.management.getSelf((info) => {
+            resolve(info);
+          });
+        })
+      })();
+    `)).to.eventually.have.property('id');
+  });
+
   it('can open WebSQLDatabase in a background page', async () => {
     const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
     const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });