blob: 8852278a0ed7ebd108989d6e84457dfb18583d66 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/enterprise/data_protection/data_protection_navigation_controller.h"
#include "base/feature_list.h"
#include "chrome/browser/enterprise/data_protection/data_protection_navigation_observer.h"
#include "chrome/browser/enterprise/watermark/settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "components/enterprise/watermarking/content/watermark_text_container.h"
#include "components/enterprise/watermarking/watermark.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
namespace {
// The preferred way to fetch the browser pointer is using `FindBrowserWithTab`.
// However, there are some code paths where `TabHelpers` is constructed before
// the `WebContents` instance is attached to the tab. In the implementation
// below, we prioritize using the tab to obtain the `Browser` ptr, but fallback
// to using the profile to do so if that fails. This is a workaround that is
// required as long as the `DataProtectionNavigationController` is constructed
// by `TabHelpers`.
Browser* GetBrowser(content::WebContents* web_contents) {
DCHECK(web_contents);
Browser* browser = chrome::FindBrowserWithTab(web_contents);
if (browser) {
return browser;
}
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (!profile) {
return nullptr;
}
return chrome::FindBrowserWithProfile(profile);
}
} // namespace
namespace enterprise_data_protection {
DataProtectionNavigationController::DataProtectionNavigationController(
tabs::TabInterface* tab_interface)
: content::WebContentsObserver(nullptr), tab_interface_(tab_interface) {
Observe(tab_interface->GetContents());
tab_subscriptions_.push_back(tab_interface_->RegisterDidActivate(
base::BindRepeating(&DataProtectionNavigationController::TabForegrounded,
weak_ptr_factory_.GetWeakPtr())));
tab_subscriptions_.push_back(
tab_interface_->RegisterWillDiscardContents(base::BindRepeating(
&DataProtectionNavigationController::WillDiscardContents,
weak_ptr_factory_.GetWeakPtr())));
// Fetch the protection settings for the current page.
enterprise_data_protection::DataProtectionNavigationObserver::
ApplyDataProtectionSettings(
Profile::FromBrowserContext(
tab_interface_->GetContents()->GetBrowserContext()),
tab_interface_->GetContents(),
base::BindOnce(
&DataProtectionNavigationController::ApplyDataProtectionSettings,
weak_ptr_factory_.GetWeakPtr(),
tab_interface_->GetContents()->GetWeakPtr()));
// If there happens to be a navigation in process then that will be missed,
// since DidStartNavigation does not trigger.
}
void DataProtectionNavigationController::SetCallbackForTesting(
base::OnceClosure closure) {
on_delay_apply_data_protection_settings_if_empty_called_for_testing_ =
std::move(closure);
}
void DataProtectionNavigationController::TabForegrounded(
tabs::TabInterface* tab) {
content::WebContents* contents = tab->GetContents();
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
enterprise_data_protection::DataProtectionNavigationObserver::
ApplyDataProtectionSettings(
profile, contents,
base::BindOnce(
&DataProtectionNavigationController::ApplyDataProtectionSettings,
weak_ptr_factory_.GetWeakPtr(), web_contents()->GetWeakPtr()));
}
void DataProtectionNavigationController::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
Browser* browser = GetBrowser(web_contents());
if (!browser) {
return;
}
enterprise_data_protection::DataProtectionNavigationObserver::
CreateForNavigationIfNeeded(
browser->profile(), navigation_handle,
base::BindOnce(&DataProtectionNavigationController::
ApplyDataProtectionSettingsOrDelayIfEmpty,
weak_ptr_factory_.GetWeakPtr(),
web_contents()->GetWeakPtr()));
}
void DataProtectionNavigationController::
ApplyDataProtectionSettingsOrDelayIfEmpty(
base::WeakPtr<content::WebContents> expected_web_contents,
const enterprise_data_protection::UrlSettings& settings) {
// If discarded, do nothing.
if (!expected_web_contents || expected_web_contents.get() != web_contents()) {
return;
}
// If the tab is in the background, do nothing.
if (!tab_interface_->IsActivated()) {
return;
}
Browser* browser = GetBrowser(web_contents());
if (!browser) {
return;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
if (!browser_view) {
return;
}
#if BUILDFLAG(ENTERPRISE_SCREENSHOT_PROTECTION)
if (!settings.allow_screenshots) {
browser_view->ApplyScreenshotSettings(settings.allow_screenshots);
} else {
// Screenshot protection should be cleared. Delay that until the page
// finishes loading.
clear_screenshot_protection_on_page_load_ = true;
}
#endif // BUILDFLAG(ENTERPRISE_SCREENSHOT_PROTECTION)
// Regardless of whether watermark text is empty, attach it as web contents
// user data so that other browser process code can draw watermarks outside
// of the context of a navigation (ex. when printing).
enterprise_watermark::WatermarkBlock block =
enterprise_watermark::DrawWatermarkToPaintRecord(
settings.watermark_text, enterprise_watermark::GetFillColor(),
enterprise_watermark::GetOutlineColor());
enterprise_watermark::WatermarkTextContainer::CreateForWebContents(
expected_web_contents.get());
enterprise_watermark::WatermarkTextContainer::FromWebContents(
expected_web_contents.get())
->SetWatermarkText(
block.record.ToSkPicture(SkRect::MakeWH(block.width, block.height)),
block.width, block.height);
if (!settings.watermark_text.empty()) {
browser_view->ApplyWatermarkSettings(settings.watermark_text);
} else {
// The watermark string should be cleared. Delay that until the page
// finishes loading.
clear_watermark_text_on_page_load_ = true;
}
if (!on_delay_apply_data_protection_settings_if_empty_called_for_testing_
.is_null()) {
std::move(
on_delay_apply_data_protection_settings_if_empty_called_for_testing_)
.Run();
}
}
void DataProtectionNavigationController::ApplyDataProtectionSettings(
base::WeakPtr<content::WebContents> expected_web_contents,
const enterprise_data_protection::UrlSettings& settings) {
// If the tab was discarded, do nothing.
if (!expected_web_contents || web_contents() != expected_web_contents.get()) {
return;
}
// If the tab is in the background, do nothing.
if (!tab_interface_->IsActivated()) {
return;
}
Browser* browser = GetBrowser(web_contents());
if (!browser) {
return;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
if (!browser_view) {
return;
}
browser_view->ApplyWatermarkSettings(settings.watermark_text);
#if BUILDFLAG(ENTERPRISE_SCREENSHOT_PROTECTION)
browser_view->ApplyScreenshotSettings(settings.allow_screenshots);
#endif // BUILDFLAG(ENTERPRISE_SCREENSHOT_PROTECTION)
}
void DataProtectionNavigationController::
DocumentOnLoadCompletedInPrimaryMainFrame() {
// It is possible for `clear_watermark_text_on_page_load_` to be set to false
// even when the watermark should be cleared. However, in this case there
// is a queued call to `ApplyDataProtectionSettings()` which will correctly
// reset the watermark. The scenario is as followed:
//
// 1/ User is viewing a page in Tab A that is watermarked.
// 2/ User loads a page that should not be watermarked into Tab A.
// 3/ `DelayApplyDataProtectionSettingsIfEmpty()` is called at navigation
// finish time which sets clear_watermark_text_on_page_load_=true.
// `DocumentOnLoadCompletedInPrimaryMainFrame()` will be called later.
// 4/ User switches to Tab B, which may or may not be watermarked.
// This calls `ApplyDataProtectionSettings()` setting the watermark
// appropriate to Tab B and sets clear_watermark_text_on_page_load_=false.
// 5/ User switches back to Tab A (which shows a page that should not be
// watermarked, as described in step 2 above). This also calls
// `ApplyDataProtectionSettings()` setting the watermark
// appropriate to Tab A (i.e. clears the watermark) and sets
// clear_watermark_text_on_page_load_=false.
// 6/ `DocumentOnLoadCompletedInPrimaryMainFrame()` is eventually called
// which does nothing because clear_watermark_text_on_page_load_==false.
// However, the watermark is already cleared in step #5.
//
// Note that steps #5 and #6 are racy but the final outcome is correct
// regardless of the order in which they execute.
if (!tab_interface_->IsActivated()) {
return;
}
Browser* browser = GetBrowser(web_contents());
if (!browser) {
return;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
if (!browser_view) {
return;
}
if (clear_watermark_text_on_page_load_) {
browser_view->ApplyWatermarkSettings(std::string());
clear_watermark_text_on_page_load_ = false;
}
#if BUILDFLAG(ENTERPRISE_SCREENSHOT_PROTECTION)
if (clear_screenshot_protection_on_page_load_) {
browser_view->ApplyScreenshotSettings(true);
clear_screenshot_protection_on_page_load_ = false;
}
#endif
}
// Called when the associated tab will enter the background.
void DataProtectionNavigationController::WillDiscardContents(
tabs::TabInterface* tab,
content::WebContents* old_contents,
content::WebContents* new_contents) {
WebContentsObserver::Observe(new_contents);
}
DataProtectionNavigationController::~DataProtectionNavigationController() =
default;
} // namespace enterprise_data_protection