blob: 073912f5f2a888368e5bf6f47a9b397098c352a8 [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 <memory>
#include <string>
#include <vector>
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/controlled_frame/controlled_frame_permission_request_test_base.h"
#include "chrome/browser/hid/chrome_hid_delegate.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/hid/hid_chooser_controller.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/download/public/common/download_item.h"
#include "components/permissions/mock_chooser_controller_view.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "extensions/common/extension_features.h"
#include "services/device/public/cpp/test/fake_hid_manager.h"
#include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-forward.h"
using testing::Contains;
using testing::StartsWith;
namespace controlled_frame {
using ControlledFramePermissionRequestTest =
ControlledFramePermissionRequestTestBase;
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestTest, Camera) {
PermissionRequestTestCase test_case;
test_case.test_script = R"(
(async function() {
const constraints = { video: true };
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
if(stream.getVideoTracks().length > 0){
return 'SUCCESS';
}
return 'FAIL: ' + stream.getVideoTracks().length + ' tracks';
} catch (err) {
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)";
test_case.permission_name = "media";
test_case.policy_features.insert(
{blink::mojom::PermissionsPolicyFeature::kCamera});
test_case.content_settings_type.insert(
{ContentSettingsType::MEDIASTREAM_CAMERA});
PermissionRequestTestParam test_param = GetParam();
RunTestAndVerify(test_case, test_param);
}
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestTest, Microphone) {
PermissionRequestTestCase test_case;
test_case.test_script = R"(
(async function() {
const constraints = { audio: true };
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
if(stream.getAudioTracks().length > 0){
return 'SUCCESS';
}
return 'FAIL: ' + stream.getAudioTracks().length + ' tracks';
} catch (err) {
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)";
test_case.permission_name = "media";
test_case.policy_features.insert(
{blink::mojom::PermissionsPolicyFeature::kMicrophone});
test_case.content_settings_type.insert(
{ContentSettingsType::MEDIASTREAM_MIC});
PermissionRequestTestParam test_param = GetParam();
RunTestAndVerify(test_case, test_param);
}
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestTest, Geolocation) {
device::ScopedGeolocationOverrider overrider(/*latitude=*/1, /*longitude=*/2);
PermissionRequestTestCase test_case;
test_case.test_script = R"(
(async function() {
try {
return await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(position) => {
resolve('SUCCESS');
},
(error) => {
const errorMessage = 'FAIL: ' + error.code + error.message;
resolve(errorMessage);
}
);
});
} catch (err) {
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)";
test_case.permission_name = "geolocation";
test_case.policy_features.insert(
{blink::mojom::PermissionsPolicyFeature::kGeolocation});
test_case.content_settings_type.insert({ContentSettingsType::GEOLOCATION});
PermissionRequestTestParam test_param = GetParam();
RunTestAndVerify(test_case, test_param);
}
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestTest,
RequestFileSystem) {
PermissionRequestTestCase test_case;
test_case.test_script = R"(
(async function() {
return new Promise((resolve) => {
window.requestFileSystem = window.requestFileSystem ||
window.webkitRequestFileSystem;
if (!window.requestFileSystem) {
resolve("FAILURE: This browser does not support requestFileSystem.");
return;
}
const storageType = window.PERSISTENT;
const requestedBytes = 1024 * 1024;
window.requestFileSystem(storageType, requestedBytes,
(fileSystem) => {
resolve("SUCCESS");
},
(error) => {
resolve("FAILURE: " + error.message);
}
);
});
})();
)";
test_case.permission_name = "filesystem";
PermissionRequestTestParam test_param = GetParam();
RunTestAndVerify(test_case, test_param);
}
class TestDownloadManagerObserver : public content::DownloadManager::Observer {
public:
void OnDownloadCreated(content::DownloadManager* manager,
download::DownloadItem* item) override {
CHECK(item);
downloads_.push_back(item->GetSuggestedFilename());
}
const std::vector<std::string>& Downloads() const { return downloads_; }
private:
std::vector<std::string> downloads_;
};
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestTest, Download) {
const std::string download_script = R"(
(function() {
try {
const link = document.createElement("a");
link.download = $1;
link.href = $1;
link.click();
return 'SUCCESS';
} catch (err) {
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)";
PermissionRequestTestCase test_case;
test_case.test_script =
content::JsReplace(download_script, "download_test.zip");
test_case.permission_name = "download";
PermissionRequestTestParam test_param = GetParam();
content::DownloadTestObserverTerminal completion_observer(
profile()->GetDownloadManager(), test_param.expected_success ? 2 : 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
TestDownloadManagerObserver download_observer;
profile()->GetDownloadManager()->AddObserver(&download_observer);
RunTestAndVerify(
test_case, test_param,
base::BindLambdaForTesting(
[](bool should_success) -> std::string { return "SUCCESS"; }));
// If |completion_observer| is expecting 0 downloads, then it will not wait
// for unexpected downloads. To avoid this, We execute another download in a
// normal tab, so at least one download will be waited on.
{
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(
web_contents, embedded_https_test_server().base_url()));
ASSERT_THAT(content::EvalJs(web_contents->GetPrimaryMainFrame(),
content::JsReplace(download_script,
"download_baseline.txt"))
.ExtractString(),
StartsWith("SUCCESS"));
}
completion_observer.WaitForFinished();
EXPECT_EQ(download_observer.Downloads().size(),
test_param.expected_success ? 2ul : 1ul);
EXPECT_THAT(download_observer.Downloads(), Contains("download_baseline.txt"));
if (download_observer.Downloads().size() == 2ul) {
EXPECT_THAT(download_observer.Downloads(), Contains("download_test.zip"));
}
profile()->GetDownloadManager()->RemoveObserver(&download_observer);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/
,
ControlledFramePermissionRequestTest,
testing::ValuesIn(
GetDefaultPermissionRequestTestParams()),
[](const testing::TestParamInfo<
PermissionRequestTestParam>& info) {
return info.param.name;
});
class MockHidDelegate : public ChromeHidDelegate {
public:
// Simulates opening the HID device chooser dialog and selecting an item. The
// chooser automatically selects the device under index 0.
void OnWebViewHidPermissionRequestCompleted(
base::WeakPtr<HidChooser> chooser,
content::GlobalRenderFrameHostId embedder_rfh_id,
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters,
content::HidChooser::Callback callback,
bool allow) override {
if (!allow) {
std::move(callback).Run(std::vector<device::mojom::HidDeviceInfoPtr>());
return;
}
auto* render_frame_host = content::RenderFrameHost::FromID(embedder_rfh_id);
ASSERT_TRUE(render_frame_host);
chooser_controller_ = std::make_unique<HidChooserController>(
render_frame_host, std::move(filters), std::move(exclusion_filters),
std::move(callback));
mock_chooser_view_ =
std::make_unique<permissions::MockChooserControllerView>();
chooser_controller_->set_view(mock_chooser_view_.get());
EXPECT_CALL(*mock_chooser_view_.get(), OnOptionsInitialized)
.WillOnce(
testing::Invoke([this] { chooser_controller_->Select({0}); }));
}
private:
std::unique_ptr<HidChooserController> chooser_controller_;
std::unique_ptr<permissions::MockChooserControllerView> mock_chooser_view_;
};
class TestContentBrowserClient : public ChromeContentBrowserClient {
public:
// ContentBrowserClient:
content::HidDelegate* GetHidDelegate() override { return &delegate_; }
private:
MockHidDelegate delegate_;
};
class ControlledFramePermissionRequestWebHidTest
: public ControlledFramePermissionRequestTest {
public:
void SetUpOnMainThread() override {
ControlledFramePermissionRequestTest::SetUpOnMainThread();
original_client_ = content::SetBrowserClientForTesting(&overriden_client_);
mojo::PendingRemote<device::mojom::HidManager> pending_remote;
hid_manager_.Bind(pending_remote.InitWithNewPipeAndPassReceiver());
base::test::TestFuture<std::vector<device::mojom::HidDeviceInfoPtr>>
devices_future;
auto* chooser_context = HidChooserContextFactory::GetForProfile(profile());
chooser_context->SetHidManagerForTesting(std::move(pending_remote),
devices_future.GetCallback());
ASSERT_TRUE(devices_future.Wait());
hid_manager_.CreateAndAddDevice("1", 0, 0, "Test HID Device", "",
device::mojom::HidBusType::kHIDBusTypeUSB);
}
~ControlledFramePermissionRequestWebHidTest() override {
content::SetBrowserClientForTesting(original_client_.get());
}
private:
base::test::ScopedFeatureList scoped_feature_list_{
extensions_features::kEnableWebHidInWebView};
TestContentBrowserClient overriden_client_;
raw_ptr<content::ContentBrowserClient> original_client_ = nullptr;
device::FakeHidManager hid_manager_;
};
IN_PROC_BROWSER_TEST_P(ControlledFramePermissionRequestWebHidTest, WebHid) {
PermissionRequestTestCase test_case;
test_case.test_script = R"(
(async function () {
try {
const device_filters = [{vendorId: 0}];
const device = await navigator.hid.requestDevice({
filters: device_filters});
if (device.length > 0){
return 'SUCCESS';
}
return 'FAIL: device length ' + device.length;
} catch (error) {
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)";
test_case.permission_name = "hid";
test_case.policy_features.insert(
{blink::mojom::PermissionsPolicyFeature::kHid});
// No embedder content settings for WebHid.
PermissionRequestTestParam test_param = GetParam();
RunTestAndVerify(test_case, test_param);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/
,
ControlledFramePermissionRequestWebHidTest,
testing::ValuesIn(
GetDefaultPermissionRequestTestParams()),
[](const testing::TestParamInfo<
PermissionRequestTestParam>& info) {
return info.param.name;
});
} // namespace controlled_frame