blob: 13d0ba6e22023ee05005b7461f7f1b3610936b71 [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/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/download/download_browsertest_utils.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/slow_download_http_response.h"
#include "content/public/test/url_loader_monitor.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/point_f.h"
#include "url/gurl.h"
using content::DownloadManager;
using content::URLLoaderMonitor;
using content::WebContents;
using download::DownloadItem;
namespace {
class FencedFrameDownloadTest : public DownloadTestBase {
public:
FencedFrameDownloadTest() = default;
~FencedFrameDownloadTest() override = default;
FencedFrameDownloadTest(const FencedFrameDownloadTest&) = delete;
FencedFrameDownloadTest& operator=(const FencedFrameDownloadTest&) = delete;
void SetUpOnMainThread() override {
DownloadTestBase::SetUpOnMainThread();
// Add content/test/data for cross_site_iframe_factory.html.
https_test_server()->ServeFilesFromSourceDirectory("content/test/data");
https_test_server()->ServeFilesFromDirectory(GetTestDataDirectory());
}
WebContents* GetWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}
private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};
// This test verifies when fenced frame untrusted network is disabled
// immediately after user right clicks and selects "Save Image As...", the
// download request is interrupted.
IN_PROC_BROWSER_TEST_F(FencedFrameDownloadTest,
NetworkCutoffInterruptSaveImageAs) {
// Disable SafeBrowsing for testing.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
false);
ASSERT_TRUE(https_test_server()->Start());
EnableFileChooser(true);
// Sanity check that there is no downloads at the start of the test.
ASSERT_TRUE(VerifyNoDownloads());
// Navigate the fenced frame to a page with an image element.
GURL fenced_frame_url(
https_test_server()->GetURL("a.test", "/test_visual.html"));
GURL main_url(https_test_server()->GetURL(
"a.test",
base::StringPrintf("/cross_site_iframe_factory.html?a.test(%s{fenced})",
fenced_frame_url.spec().c_str())));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// Get fenced frame render frame host.
std::vector<content::RenderFrameHost*> child_frames =
fenced_frame_test_helper().GetChildFencedFrameHosts(
GetWebContents()->GetPrimaryMainFrame());
ASSERT_EQ(child_frames.size(), 1u);
content::RenderFrameHost* fenced_frame_rfh = child_frames[0];
ASSERT_EQ(fenced_frame_rfh->GetLastCommittedURL(), fenced_frame_url);
// To avoid flakiness and ensure fenced_frame_rfh is ready for hit testing.
content::WaitForHitTestData(fenced_frame_rfh);
// Upon context menu shown, disable fenced frame untrusted network access.
// Then execute "Save Image As...".
ContextMenuWaiter context_menu_waiter(
IDC_CONTENT_CONTEXT_SAVEIMAGEAS,
base::BindLambdaForTesting([fenced_frame_rfh]() {
ASSERT_TRUE(ExecJs(fenced_frame_rfh, R"(
(async () => {
return window.fence.disableUntrustedNetwork();
})();
)"));
}));
// Create observer for the download.
auto download_observer =
std::make_unique<content::DownloadTestObserverInterrupted>(
DownloadManagerForBrowser(browser()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
// Click inside the fenced frame.
const gfx::PointF image_element(15, 15);
// Right-click on the image element to open the context menu.
content::test::SimulateClickInFencedFrameTree(
fenced_frame_rfh, blink::WebMouseEvent::Button::kRight, image_element);
// Wait for the context menu to be shown.
context_menu_waiter.WaitForMenuOpenAndClose();
// The download request should be interrupted.
download_observer->WaitForFinished();
std::vector<raw_ptr<DownloadItem, VectorExperimental>> download_items;
GetDownloads(browser(), &download_items);
ASSERT_EQ(download_items.size(), 1UL);
ASSERT_EQ(download_items[0]->GetState(), DownloadItem::INTERRUPTED);
EXPECT_EQ(download_items[0]->GetLastReason(),
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
}
// This test verifies when fenced frame untrusted network is disabled
// immediately after user right clicks and before "Save Audio As..." is
// selected, the download request is interrupted.
IN_PROC_BROWSER_TEST_F(FencedFrameDownloadTest,
NetworkCutoffInterruptSaveAudioAs) {
// Disable SafeBrowsing for testing.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
false);
ASSERT_TRUE(https_test_server()->Start());
EnableFileChooser(true);
// Sanity check that there is no downloads at the start of the test.
ASSERT_TRUE(VerifyNoDownloads());
// Navigate the fenced frame to a page with an audio element.
GURL fenced_frame_url(
https_test_server()->GetURL("a.test", "/accessibility/html/audio.html"));
GURL main_url(https_test_server()->GetURL(
"a.test",
base::StringPrintf("/cross_site_iframe_factory.html?a.test(%s{fenced})",
fenced_frame_url.spec().c_str())));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// Get fenced frame render frame host.
std::vector<content::RenderFrameHost*> child_frames =
fenced_frame_test_helper().GetChildFencedFrameHosts(
GetWebContents()->GetPrimaryMainFrame());
ASSERT_EQ(child_frames.size(), 1u);
content::RenderFrameHost* fenced_frame_rfh = child_frames[0];
ASSERT_EQ(fenced_frame_rfh->GetLastCommittedURL(), fenced_frame_url);
// To avoid flakiness and ensure fenced_frame_rfh is ready for hit testing.
content::WaitForHitTestData(fenced_frame_rfh);
// Upon context menu shown, disable fenced frame untrusted network access.
// Then execute "Save Audio As...".
ContextMenuWaiter context_menu_waiter(
IDC_CONTENT_CONTEXT_SAVEAVAS,
base::BindLambdaForTesting([fenced_frame_rfh]() {
ASSERT_TRUE(ExecJs(fenced_frame_rfh, R"(
(async () => {
return window.fence.disableUntrustedNetwork();
})();
)"));
}));
// Create observer for the download.
auto download_observer =
std::make_unique<content::DownloadTestObserverInterrupted>(
DownloadManagerForBrowser(browser()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
// Click inside the fenced frame.
const gfx::PointF audio_element(15, 15);
// Right-click on the audio element to open the context menu.
content::test::SimulateClickInFencedFrameTree(
fenced_frame_rfh, blink::WebMouseEvent::Button::kRight, audio_element);
// Wait for the context menu to be shown.
context_menu_waiter.WaitForMenuOpenAndClose();
// The download request should be interrupted.
download_observer->WaitForFinished();
std::vector<raw_ptr<DownloadItem, VectorExperimental>> download_items;
GetDownloads(browser(), &download_items);
ASSERT_EQ(download_items.size(), 1UL);
ASSERT_EQ(download_items[0]->GetState(), DownloadItem::INTERRUPTED);
EXPECT_EQ(download_items[0]->GetLastReason(),
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
}
// This test verifies when fenced frame untrusted network is disabled
// immediately after user right clicks and before "Save Video As..." is
// selected, the download request is interrupted.
IN_PROC_BROWSER_TEST_F(FencedFrameDownloadTest,
NetworkCutoffInterruptSaveVideoAs) {
// Disable SafeBrowsing for testing.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
false);
ASSERT_TRUE(https_test_server()->Start());
EnableFileChooser(true);
// Sanity check that there is no downloads at the start of the test.
ASSERT_TRUE(VerifyNoDownloads());
// Navigate the fenced frame to a page with a video element.
GURL fenced_frame_url(https_test_server()->GetURL(
"a.test", "/media/video-player-autoplay.html"));
GURL main_url(https_test_server()->GetURL(
"a.test",
base::StringPrintf("/cross_site_iframe_factory.html?a.test(%s{fenced})",
fenced_frame_url.spec().c_str())));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// Get fenced frame render frame host.
std::vector<content::RenderFrameHost*> child_frames =
fenced_frame_test_helper().GetChildFencedFrameHosts(
GetWebContents()->GetPrimaryMainFrame());
ASSERT_EQ(child_frames.size(), 1u);
content::RenderFrameHost* fenced_frame_rfh = child_frames[0];
ASSERT_EQ(fenced_frame_rfh->GetLastCommittedURL(), fenced_frame_url);
// To avoid flakiness and ensure fenced_frame_rfh is ready for hit testing.
content::WaitForHitTestData(fenced_frame_rfh);
// Upon context menu shown, disable fenced frame untrusted network access.
// Then execute "Save Video As...".
ContextMenuWaiter context_menu_waiter(
IDC_CONTENT_CONTEXT_SAVEAVAS,
base::BindLambdaForTesting([fenced_frame_rfh]() {
ASSERT_TRUE(ExecJs(fenced_frame_rfh, R"(
(async () => {
return window.fence.disableUntrustedNetwork();
})();
)"));
}));
// Create observer for the download.
auto download_observer =
std::make_unique<content::DownloadTestObserverInterrupted>(
DownloadManagerForBrowser(browser()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
// Click inside the fenced frame.
const gfx::PointF video_element(15, 15);
// Right-click on the video element to open the context menu.
content::test::SimulateClickInFencedFrameTree(
fenced_frame_rfh, blink::WebMouseEvent::Button::kRight, video_element);
// Wait for the context menu to be shown.
context_menu_waiter.WaitForMenuOpenAndClose();
// The download request should be interrupted.
download_observer->WaitForFinished();
std::vector<raw_ptr<DownloadItem, VectorExperimental>> download_items;
GetDownloads(browser(), &download_items);
ASSERT_EQ(download_items.size(), 1UL);
ASSERT_EQ(download_items[0]->GetState(), DownloadItem::INTERRUPTED);
EXPECT_EQ(download_items[0]->GetLastReason(),
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
}
// This test verifies when fenced frame untrusted network is disabled during an
// in-progress download request, the download is interrupted.
IN_PROC_BROWSER_TEST_F(FencedFrameDownloadTest,
NetworkCutoffInterruptInProgressSaveLinkAsDownload) {
// Disable SafeBrowsing for testing.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
false);
DownloadManager* manager = DownloadManagerForBrowser(browser());
// Configure the test server to simulate a slow download.
https_test_server()->RegisterRequestHandler(base::BindRepeating(
&content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
EXPECT_TRUE(https_test_server()->Start());
EXPECT_EQ(0, manager->BlockingShutdownCount());
EXPECT_EQ(0, manager->InProgressCount());
EnableFileChooser(true);
// Sanity check that there is no downloads at the start of the test.
ASSERT_TRUE(VerifyNoDownloads());
// Navigate the fenced frame to a page with an anchor element.
GURL fenced_frame_url =
https_test_server()->GetURL("a.test", "/download-anchor-slow.html");
GURL main_url(https_test_server()->GetURL(
"a.test",
base::StringPrintf("/cross_site_iframe_factory.html?a.test(%s{fenced})",
fenced_frame_url.spec().c_str())));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
// Get fenced frame render frame host.
std::vector<content::RenderFrameHost*> child_frames =
fenced_frame_test_helper().GetChildFencedFrameHosts(
GetWebContents()->GetPrimaryMainFrame());
ASSERT_EQ(child_frames.size(), 1u);
content::RenderFrameHost* fenced_frame_rfh = child_frames[0];
ASSERT_EQ(fenced_frame_rfh->GetLastCommittedURL(), fenced_frame_url);
// To avoid flakiness and ensure fenced_frame_rfh is ready for hit testing.
content::WaitForHitTestData(fenced_frame_rfh);
// Upon context menu shown, execute "Save Link As...".
ContextMenuWaiter context_menu_waiter(IDC_CONTENT_CONTEXT_SAVELINKAS);
// Create observer for the download.
auto in_progress_download_observer =
std::make_unique<content::DownloadTestObserverInProgress>(manager, 1);
auto interrupt_download_observer =
std::make_unique<content::DownloadTestObserverInterrupted>(
DownloadManagerForBrowser(browser()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_IGNORE);
// Monitor requests to the anchor element href URL.
GURL anchor_href =
GURL(EvalJs(fenced_frame_rfh, "document.getElementById('anchor').href")
.ExtractString());
URLLoaderMonitor monitor({anchor_href});
// Get the coordinate of the anchor element.
const gfx::PointF anchor_element =
GetCenterCoordinatesOfElementWithId(fenced_frame_rfh, "anchor");
// Right-click on the anchor element to open the context menu.
content::test::SimulateClickInFencedFrameTree(
fenced_frame_rfh, blink::WebMouseEvent::Button::kRight, anchor_element);
// Wait for the context menu to be shown.
context_menu_waiter.WaitForMenuOpenAndClose();
// Expect the slow download is in progress.
in_progress_download_observer->WaitForFinished();
EXPECT_EQ(1u, in_progress_download_observer->NumDownloadsSeenInState(
DownloadItem::IN_PROGRESS));
// Disable fenced frame untrusted network access.
ASSERT_TRUE(ExecJs(fenced_frame_rfh, R"(
(async () => {
return window.fence.disableUntrustedNetwork();
})();
)"));
// The request is stopped with fenced frame network revocation error code.
EXPECT_EQ(monitor.WaitForRequestCompletion(anchor_href).error_code,
net::ERR_NETWORK_ACCESS_REVOKED);
// The download request should be interrupted because of the network status
// change.
interrupt_download_observer->WaitForFinished();
DownloadManager::DownloadVector download_items;
GetDownloads(browser(), &download_items);
ASSERT_EQ(download_items.size(), 1UL);
ASSERT_EQ(download_items[0]->GetState(), DownloadItem::INTERRUPTED);
EXPECT_EQ(download_items[0]->GetLastReason(),
download::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED);
}
} // namespace