Skip to content

Commit

Permalink
Modify LacrosExtensionAppsPublisher to publish CapabilityAccess.
Browse files Browse the repository at this point in the history
Similar as ExtensionAppsChromeOs, modify LacrosExtensionAppsPublisher
to observe MediaStreamCaptureIndicator to keep track media uses, and
update AppCapabilityAccess for Chrome browser and Chrome apps.

Add browser tests to verify the change.

BUG=1431065, b:281635653

Change-Id: I87e259ee79d409163d3a49df33ba43a3dc330851
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4790507
Commit-Queue: Nancy Wang <nancylingwang@chromium.org>
Reviewed-by: Erik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1186924}
  • Loading branch information
Nancy Wang authored and Chromium LUCI CQ committed Aug 22, 2023
1 parent f126ce4 commit bd56715
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 1 deletion.
77 changes: 77 additions & 0 deletions chrome/browser/lacros/lacros_extension_apps_publisher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/lacros/lacros_extensions_util.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/lacros/window_utility.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chromeos/crosapi/mojom/app_window_tracker.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/app_constants/constants.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "components/services/app_service/public/cpp/intent_filter.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_prefs.h"
Expand Down Expand Up @@ -403,6 +408,13 @@ void LacrosExtensionAppsPublisher::Initialize() {
profile_trackers_[profile] =
std::make_unique<ProfileTracker>(profile, this, which_type_);
}

// Only track the media usage for the chrome apps.
if (which_type_.IsChromeApps()) {
media_dispatcher_.Observe(MediaCaptureDevicesDispatcher::GetInstance()
->GetMediaStreamCaptureIndicator()
.get());
}
}

bool LacrosExtensionAppsPublisher::InitializeCrosapi() {
Expand Down Expand Up @@ -442,6 +454,11 @@ void LacrosExtensionAppsPublisher::Publish(std::vector<apps::AppPtr> apps) {
publisher_->OnApps(std::move(apps));
}

void LacrosExtensionAppsPublisher::PublishCapabilityAccesses(
std::vector<apps::CapabilityAccessPtr> accesses) {
publisher_->OnCapabilityAccesses(std::move(accesses));
}

void LacrosExtensionAppsPublisher::OnAppWindowAdded(
const std::string& app_id,
const std::string& window_id) {
Expand Down Expand Up @@ -501,3 +518,63 @@ void LacrosExtensionAppsPublisher::UpdateAppWindowMode(
DCHECK(matched != profile_trackers_.end());
matched->second->Publish(extension, apps::Readiness::kReady);
}

void LacrosExtensionAppsPublisher::OnIsCapturingVideoChanged(
content::WebContents* web_contents,
bool is_capturing_video) {
auto app_id = MaybeGetAppId(web_contents);
if (!app_id.has_value()) {
return;
}

auto result = media_requests_.UpdateCameraState(app_id.value(), web_contents,
is_capturing_video);
ModifyCapabilityAccess(app_id.value(), result.camera, result.microphone);
}

void LacrosExtensionAppsPublisher::OnIsCapturingAudioChanged(
content::WebContents* web_contents,
bool is_capturing_audio) {
auto app_id = MaybeGetAppId(web_contents);
if (!app_id.has_value()) {
return;
}

auto result = media_requests_.UpdateMicrophoneState(
app_id.value(), web_contents, is_capturing_audio);
ModifyCapabilityAccess(app_id.value(), result.camera, result.microphone);
}

absl::optional<std::string> LacrosExtensionAppsPublisher::MaybeGetAppId(
content::WebContents* web_contents) {
// The web app publisher is responsible to handle `web_contents` for web
// apps.
const web_app::AppId* web_app_id =
web_app::WebAppTabHelper::GetAppId(web_contents);
if (web_app_id) {
return absl::nullopt;
}

const auto* extension =
lacros_extensions_util::MaybeGetExtension(web_contents);
return (extension && which_type_.Matches(extension))
? absl::make_optional<std::string>(extension->id())
: absl::nullopt;
}

void LacrosExtensionAppsPublisher::ModifyCapabilityAccess(
const std::string& app_id,
absl::optional<bool> accessing_camera,
absl::optional<bool> accessing_microphone) {
if (!accessing_camera.has_value() && !accessing_microphone.has_value()) {
return;
}

std::vector<apps::CapabilityAccessPtr> capability_accesses;
auto capability_access = std::make_unique<apps::CapabilityAccess>(app_id);
capability_access->camera = accessing_camera;
capability_access->microphone = accessing_microphone;
capability_accesses.push_back(std::move(capability_access));

PublishCapabilityAccesses(std::move(capability_accesses));
}
38 changes: 37 additions & 1 deletion chrome/browser/lacros/lacros_extension_apps_publisher.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
#include <vector>

#include "base/scoped_observation.h"
#include "chrome/browser/apps/app_service/media_requests.h"
#include "chrome/browser/lacros/for_which_extension_type.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_manager_observer.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chromeos/crosapi/mojom/app_service.mojom.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace content {
class WebContents;
} // namespace content

// This class tracks Chrome apps [i.e. extension-based apps, AKA v2 packaged
// apps] or extensions running in Lacros, and forwards metadata about these
Expand All @@ -39,7 +47,9 @@
//
// See LacrosExtensionAppsController for the class responsible for receiving
// events from Ash.
class LacrosExtensionAppsPublisher : public ProfileManagerObserver {
class LacrosExtensionAppsPublisher
: public ProfileManagerObserver,
public MediaStreamCaptureIndicator::Observer {
public:
static std::unique_ptr<LacrosExtensionAppsPublisher> MakeForChromeApps();
static std::unique_ptr<LacrosExtensionAppsPublisher> MakeForExtensions();
Expand Down Expand Up @@ -71,6 +81,11 @@ class LacrosExtensionAppsPublisher : public ProfileManagerObserver {
// Virtual for testing.
virtual void Publish(std::vector<apps::AppPtr> apps);

// Publishes differential CapabilityAccess updates to the App_Service in Ash
// via crosapi. Virtual for testing.
virtual void PublishCapabilityAccesses(
std::vector<apps::CapabilityAccessPtr> accesses);

// Notifies Ash's app window tracker of an app window construction. For Chrome
// apps only. Virtual for testing.
virtual void OnAppWindowAdded(const std::string& app_id,
Expand All @@ -95,6 +110,18 @@ class LacrosExtensionAppsPublisher : public ProfileManagerObserver {
void OnProfileMarkedForPermanentDeletion(Profile* profile) override;
void OnProfileManagerDestroying() override;

// MediaStreamCaptureIndicator::Observer
void OnIsCapturingVideoChanged(content::WebContents* web_contents,
bool is_capturing_video) override;
void OnIsCapturingAudioChanged(content::WebContents* web_contents,
bool is_capturing_audio) override;

absl::optional<std::string> MaybeGetAppId(content::WebContents* web_contents);

void ModifyCapabilityAccess(const std::string& app_id,
absl::optional<bool> accessing_camera,
absl::optional<bool> accessing_microphone);

// State to decide which extension type (e.g., Chrome Apps vs. Extensions)
// to support.
const ForWhichExtensionType which_type_;
Expand All @@ -108,6 +135,15 @@ class LacrosExtensionAppsPublisher : public ProfileManagerObserver {
// Scoped observer for the ProfileManager.
base::ScopedObservation<ProfileManager, ProfileManagerObserver>
profile_manager_observation_{this};

// Scoped observer for the MediaStreamCaptureIndicator.
base::ScopedObservation<MediaStreamCaptureIndicator,
MediaStreamCaptureIndicator::Observer>
media_dispatcher_{this};

// Tracks the media usage for each app (e.g. accessing camera, microphone) on
// a per-WebContents basis.
apps::MediaRequests media_requests_;
};

#endif // CHROME_BROWSER_LACROS_LACROS_EXTENSION_APPS_PUBLISHER_H_
112 changes: 112 additions & 0 deletions chrome/browser/lacros/lacros_extension_apps_publisher_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,30 @@
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/browser/lacros/for_which_extension_type.h"
#include "chrome/browser/lacros/lacros_extensions_util.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/ui/lacros/window_utility.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
#include "components/app_constants/constants.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/common/constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/aura/window.h"

namespace {

using Apps = std::vector<apps::AppPtr>;
using Accesses = std::vector<apps::CapabilityAccessPtr>;

// This fake intercepts and tracks all calls to Publish().
class LacrosExtensionAppsPublisherFake : public LacrosExtensionAppsPublisher {
Expand All @@ -45,14 +54,45 @@ class LacrosExtensionAppsPublisherFake : public LacrosExtensionAppsPublisher {
LacrosExtensionAppsPublisherFake& operator=(
const LacrosExtensionAppsPublisherFake&) = delete;

void VerifyAppCapabilityAccess(const std::string& app_id,
size_t count,
absl::optional<bool> accessing_camera,
absl::optional<bool> accessing_microphone) {
ASSERT_EQ(count, accesses_history().size());
Accesses& accesses = accesses_history().back();
ASSERT_EQ(1u, accesses.size());
EXPECT_EQ(app_id, accesses[0]->app_id);

if (accessing_camera.has_value()) {
ASSERT_TRUE(accesses[0]->camera.has_value());
EXPECT_EQ(accessing_camera.value(), accesses[0]->camera.value());
} else {
ASSERT_FALSE(accesses[0]->camera.has_value());
}

if (accessing_microphone.has_value()) {
ASSERT_TRUE(accesses[0]->microphone.has_value());
EXPECT_EQ(accessing_microphone.value(), accesses[0]->microphone.value());
} else {
ASSERT_FALSE(accesses[0]->microphone.has_value());
}
}

std::vector<Apps>& apps_history() { return apps_history_; }

std::vector<Accesses>& accesses_history() { return accesses_history_; }

std::map<std::string, std::string>& app_windows() { return app_windows_; }

private:
// Override to intercept calls to Publish().
void Publish(Apps apps) override { apps_history_.push_back(std::move(apps)); }

// Override to intercept calls to PublishCapabilityAccesses().
void PublishCapabilityAccesses(Accesses accesses) override {
accesses_history_.push_back(std::move(accesses));
}

// Override to intercept calls to OnAppWindowAdded().
void OnAppWindowAdded(const std::string& app_id,
const std::string& window_id) override {
Expand All @@ -73,6 +113,10 @@ class LacrosExtensionAppsPublisherFake : public LacrosExtensionAppsPublisher {
// Holds the contents of all calls to Publish() in chronological order.
std::vector<Apps> apps_history_;

// Holds the contents of all calls to PublishCapabilityAccesses() in
// chronological order.
std::vector<Accesses> accesses_history_;

// Holds the list of currently showing app windows, as seen by
// OnAppWindowAdded() and OnAppWindowRemoved(). The key is the window_id and
// the value is the app_id.
Expand Down Expand Up @@ -103,6 +147,34 @@ void VerifyOnlyDefaultAppsPublished(
ASSERT_FALSE(default_app->is_platform_app.value());
}

// Adds a fake media device with the specified `stream_type` and starts
// capturing. Returns a closure to stop the capturing.
base::OnceClosure StartMediaCapture(content::WebContents* web_contents,
blink::mojom::MediaStreamType stream_type) {
blink::mojom::StreamDevices fake_devices;
blink::MediaStreamDevice device(stream_type, "fake_device", "fake_device");

if (blink::IsAudioInputMediaType(stream_type)) {
fake_devices.audio_device = device;
} else {
fake_devices.video_device = device;
}

std::unique_ptr<content::MediaStreamUI> ui =
MediaCaptureDevicesDispatcher::GetInstance()
->GetMediaStreamCaptureIndicator()
->RegisterMediaStream(web_contents, fake_devices);

ui->OnStarted(base::RepeatingClosure(),
content::MediaStreamUI::SourceCallback(),
/*label=*/std::string(), /*screen_capture_ids=*/{},
content::MediaStreamUI::StateChangeCallback());

return base::BindOnce(
[](std::unique_ptr<content::MediaStreamUI> ui) { ui.reset(); },
std::move(ui));
}

using LacrosExtensionAppsPublisherTest = extensions::ExtensionBrowserTest;

// When publisher is created and initialized, only chrome default apps
Expand Down Expand Up @@ -302,4 +374,44 @@ IN_PROC_BROWSER_TEST_F(LacrosExtensionAppsPublisherTest, PreLaunchAppWindow) {
}
}

IN_PROC_BROWSER_TEST_F(LacrosExtensionAppsPublisherTest,
RequestAccessingForPlatformApp) {
const extensions::Extension* extension =
LoadAndLaunchApp(test_data_dir_.AppendASCII("platform_apps/minimal"));
ASSERT_TRUE(extension);

std::unique_ptr<LacrosExtensionAppsPublisherFake> publisher =
std::make_unique<LacrosExtensionAppsPublisherFake>();
publisher->Initialize();

auto* registry = extensions::AppWindowRegistry::Get(profile());
extensions::AppWindow* app_window =
registry->GetCurrentAppWindowForApp(extension->id());
ASSERT_TRUE(app_window);
content::WebContents* web_contents = app_window->web_contents();
ASSERT_TRUE(web_contents);

// Request accessing the camera for `web_contents`.
base::OnceClosure video_closure = StartMediaCapture(
web_contents, blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);
publisher->VerifyAppCapabilityAccess(extension->id(), 1u, true,
absl::nullopt);

// Request accessing the microphone for `web_contents`.
base::OnceClosure audio_closure = StartMediaCapture(
web_contents, blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE);
publisher->VerifyAppCapabilityAccess(extension->id(), 2u, absl::nullopt,
true);

// Stop accessing the microphone for `web_contents`.
std::move(audio_closure).Run();
publisher->VerifyAppCapabilityAccess(extension->id(), 3u, absl::nullopt,
false);

// Stop accessing the camera for `web_contents`.
std::move(video_closure).Run();
publisher->VerifyAppCapabilityAccess(extension->id(), 4u, false,
absl::nullopt);
}

} // namespace
14 changes: 14 additions & 0 deletions chrome/browser/lacros/lacros_extensions_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"

Expand All @@ -35,6 +36,19 @@ const extensions::Extension* MaybeGetExtension(
return registry->GetInstalledExtension(extension_id);
}

const extensions::Extension* MaybeGetExtension(
content::WebContents* web_contents) {
if (!web_contents) {
return nullptr;
}

extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(
Profile::FromBrowserContext(web_contents->GetBrowserContext()));
DCHECK(registry);
const extensions::ExtensionSet& extensions = registry->enabled_extensions();
return extensions.GetAppByURL(web_contents->GetVisibleURL());
}

bool GetProfileAndExtension(const std::string& extension_id,
Profile** output_profile,
const extensions::Extension** output_extension) {
Expand Down

0 comments on commit bd56715

Please sign in to comment.