// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/display/output_protection_delegate.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/shell.h"
#include "base/functional/callback_helpers.h"
#include "ui/display/display.h"
#include "ui/display/manager/content_protection_manager.h"
#include "ui/display/manager/display_configurator.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
namespace ash {
namespace {
display::ContentProtectionManager* manager() {
return Shell::Get()->display_configurator()->content_protection_manager();
}
void MaybeSetCaptureModeWindowProtection(aura::Window* window,
uint32_t protection_mask) {
// `OutputProtectionDelegate` is not owned by ash. It is created by
// `OutputProtectionImpl` which exists in Chrome, and can invoke the delegate
// even after `Shell` has been destroyed. See b/256706119.
if (!Shell::HasInstance())
return;
CaptureModeController::Get()->SetWindowProtectionMask(window,
protection_mask);
}
} // namespace
struct OutputProtectionDelegate::ClientIdHolder {
ClientIdHolder() : id(manager()->RegisterClient()) {}
~ClientIdHolder() { manager()->UnregisterClient(id); }
const display::ContentProtectionManager::ClientId id;
};
OutputProtectionDelegate::OutputProtectionDelegate(aura::Window* window)
: window_(window),
display_id_(
display::Screen::GetScreen()->GetDisplayNearestWindow(window).id()) {
// TODO(domlaskowski): OutputProtectionImpl passes null if the RenderFrameHost
// no longer exists. Investigate removing this check in crbug.com/997270.
if (!window_)
return;
window_->AddObserver(this);
}
OutputProtectionDelegate::~OutputProtectionDelegate() {
if (!window_)
return;
window_->RemoveObserver(this);
MaybeSetCaptureModeWindowProtection(window_,
display::CONTENT_PROTECTION_METHOD_NONE);
}
void OutputProtectionDelegate::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
// Switching the primary display (either by user or by going into docked
// mode), as well as changing mirror mode may change the display on which
// the window resides without actually changing the window hierarchy (i.e.
// the root window is still the same). Hence we need to watch out for these
// situations and update |display_id_| if needed.
if (!(changed_metrics &
(display::DisplayObserver::DISPLAY_METRIC_PRIMARY |
display::DisplayObserver::DISPLAY_METRIC_MIRROR_STATE))) {
return;
}
OnWindowMayHaveMovedToAnotherDisplayOrWindow();
}
void OutputProtectionDelegate::OnWindowHierarchyChanged(
const aura::WindowObserver::HierarchyChangeParams& params) {
OnWindowMayHaveMovedToAnotherDisplayOrWindow();
}
void OutputProtectionDelegate::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window, window_);
display_observer_.reset();
window_->RemoveObserver(this);
MaybeSetCaptureModeWindowProtection(window_,
display::CONTENT_PROTECTION_METHOD_NONE);
window_ = nullptr;
}
void OutputProtectionDelegate::QueryStatus(QueryStatusCallback callback) {
if (!RegisterClientIfNecessary()) {
std::move(callback).Run(/*success=*/false,
display::DISPLAY_CONNECTION_TYPE_NONE,
display::CONTENT_PROTECTION_METHOD_NONE);
return;
}
manager()->QueryContentProtection(client_->id, display_id_,
std::move(callback));
}
void OutputProtectionDelegate::SetProtection(uint32_t protection_mask,
SetProtectionCallback callback) {
protection_mask_ = protection_mask;
// Capture mode screen recording doesn't rely on display protection, and hence
// must be informed with the new window's protection.
if (window_)
MaybeSetCaptureModeWindowProtection(window_, protection_mask);
if (!RegisterClientIfNecessary()) {
std::move(callback).Run(/*success=*/false);
return;
}
manager()->ApplyContentProtection(client_->id, display_id_, protection_mask,
std::move(callback));
}
void OutputProtectionDelegate::OnWindowMayHaveMovedToAnotherDisplayOrWindow() {
DCHECK(window_);
// The window may have moved to a display that is currently being recorded, or
// to be hosted by a browser window that is being recorded when a tab becomes
// active, so we need to refresh Capture Mode's content protection.
CaptureModeController::Get()->RefreshContentProtection();
const int64_t new_display_id =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_).id();
if (display_id_ == new_display_id)
return;
if (protection_mask_ != display::CONTENT_PROTECTION_METHOD_NONE) {
DCHECK(client_);
manager()->ApplyContentProtection(client_->id, new_display_id,
protection_mask_, base::DoNothing());
manager()->ApplyContentProtection(client_->id, display_id_,
display::CONTENT_PROTECTION_METHOD_NONE,
base::DoNothing());
}
display_id_ = new_display_id;
}
bool OutputProtectionDelegate::RegisterClientIfNecessary() {
if (!window_)
return false;
if (!client_)
client_ = std::make_unique<ClientIdHolder>();
return true;
}
} // namespace ash