diff --git a/src/DEPS b/src/DEPS index ff833729660b0..ccfd0fccd1a53 --- a/src/DEPS +++ b/src/DEPS @@ -253,7 +253,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': '29b222a3c07c541cafa459ae6886134da3493a4b', + 'angle_revision': 'bfab7e60a15dc6f72e34406d3f2a3996cd8d0be2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -292,7 +292,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. - 'freetype_revision': '034e5dbf92ea3a7ea7c9322e47a3a50ff23f7b55', + 'freetype_revision': 'bec4ef415ef07ad1fa9542978136d9863dd7a6d0', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. @@ -300,7 +300,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling HarfBuzz # and whatever else without interference from each other. - 'harfbuzz_revision': '0acf466c44143de2e9b9cc0375cb25ec67cb132f', + 'harfbuzz_revision': '61486746d3d8937c2b656c3ba72bd666fadef76c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Emoji Segmenter # and whatever else without interference from each other. diff --git a/src/base/tracing/protos/chrome_track_event.proto b/src/base/tracing/protos/chrome_track_event.proto index 1102a597da577..6e4bb3e1d6db3 --- a/src/base/tracing/protos/chrome_track_event.proto +++ b/src/base/tracing/protos/chrome_track_event.proto @@ -514,6 +514,7 @@ message BackForwardCacheCanStoreDocumentResult { CACHE_CONTROL_NO_STORE_HTTP_ONLY_COOKIE_MODIFIED = 51; NO_RESPONSE_HEAD = 52; ACTIVATION_NAVIGATION_DISALLOWED_FOR_BUG_1234857 = 53; + ERROR_DOCUMENT = 54; } optional BackForwardCacheNotRestoredReason diff --git a/src/cc/paint/paint_op_reader.cc b/src/cc/paint/paint_op_reader.cc index c56e2ea7a641d..ecc736b61defa --- a/src/cc/paint/paint_op_reader.cc +++ b/src/cc/paint/paint_op_reader.cc @@ -329,6 +329,10 @@ void PaintOpReader::Read(PaintImage* image) { SkImageInfo image_info = SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType); + if (pixel_size < image_info.computeMinByteSize()) { + SetInvalid(DeserializationError::kInsufficientPixelData); + return; + } const volatile void* pixel_data = ExtractReadableMemory(pixel_size); if (!valid_) return; diff --git a/src/cc/paint/paint_op_reader.h b/src/cc/paint/paint_op_reader.h index 201cdfde5eea3..af784145a9365 --- a/src/cc/paint/paint_op_reader.h +++ b/src/cc/paint/paint_op_reader.h @@ -180,8 +180,9 @@ class CC_PAINT_EXPORT PaintOpReader { kSharedImageProviderNoAccess = 50, kSharedImageProviderSkImageCreationFailed = 51, kZeroSkColorFilterBytes = 52, + kInsufficientPixelData = 53, - kMaxValue = kZeroSkColorFilterBytes, + kMaxValue = kInsufficientPixelData }; template diff --git a/src/chrome/browser/media/webrtc/region_capture_browsertest.cc b/src/chrome/browser/media/webrtc/region_capture_browsertest.cc index a7c8804b221b7..baa9ac61fa458 --- a/src/chrome/browser/media/webrtc/region_capture_browsertest.cc +++ b/src/chrome/browser/media/webrtc/region_capture_browsertest.cc @@ -40,6 +40,11 @@ namespace { using content::WebContents; +using testing::Bool; +using testing::Combine; +using testing::TestParamInfo; +using testing::Values; +using testing::WithParamInterface; // TODO(crbug.com/1247761): Add tests that verify excessive calls to // produceCropId() yield the empty string. @@ -67,7 +72,7 @@ enum { kServerCount // Must be last. }; -enum { +enum Tab { kMainTab, kOtherTab, kTabCount // Must be last. @@ -102,6 +107,9 @@ struct TabInfo { } void StartCaptureFromEmbeddedFrame() { + // Bring the tab into focus. This avoids getDisplayMedia rejection. + browser->tab_strip_model()->ActivateTabAt(tab_strip_index); + std::string script_result; EXPECT_TRUE(content::ExecuteScriptAndExtractString( web_contents->GetMainFrame(), "startCaptureFromEmbeddedFrame();", @@ -155,6 +163,8 @@ struct TabInfo { // detection of JS errors. class RegionCaptureBrowserTest : public WebRtcTestBase { public: + ~RegionCaptureBrowserTest() override = default; + void SetUpInProcessBrowserTestFixture() override { WebRtcTestBase::SetUpInProcessBrowserTestFixture(); @@ -215,20 +225,18 @@ class RegionCaptureBrowserTest : public WebRtcTestBase { // Set up all (necessary) tabs, loads iframes, and start capturing the // relevant tab. void SetUpTest(Frame capturing_entity, bool self_capture) { - // Main page (for self-capture). + // Other page (for other-tab-capture). + SetUpPage("/webrtc/region_capture_other_main.html", + servers_[kOtherPageTopLevelDocument].get(), + "/webrtc/region_capture_other_embedded.html", + servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]); + + // Main page (for self-capture). Instantiate it second to help it get focus. SetUpPage("/webrtc/region_capture_main.html", servers_[kMainPageTopLevelDocument].get(), "/webrtc/region_capture_embedded.html", servers_[kMainPageEmbeddedDocument].get(), &tabs_[kMainTab]); - if (!self_capture) { - // Other page (for other-tab-capture). - SetUpPage("/webrtc/region_capture_other_main.html", - servers_[kOtherPageTopLevelDocument].get(), - "/webrtc/region_capture_other_embedded.html", - servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]); - } - DCHECK(command_line_); command_line_->AppendSwitchASCII( switches::kAutoSelectTabCaptureSourceByTitle, @@ -376,28 +384,6 @@ IN_PROC_BROWSER_TEST_F( EXPECT_EQ(tab.CropTo(""), "top-level-crop-success"); } -IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, - CropToRejectedIfElementInAnotherTabTopLevel) { - SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false); - - const std::string crop_id = - tabs_[kOtherTab].ProduceCropId(Frame::kTopLevelDocument); - ASSERT_THAT(crop_id, IsValidCropId()); - - EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error"); -} - -IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, - CropToRejectedIfElementInAnotherTabEmbeddedFrame) { - SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false); - - const std::string crop_id = - tabs_[kOtherTab].ProduceCropId(Frame::kEmbeddedFrame); - ASSERT_THAT(crop_id, IsValidCropId()); - - EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error"); -} - IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInTopLevelDocument) { SetUpTest(Frame::kNone, /*self_capture=*/false); TabInfo& tab = tabs_[kMainTab]; @@ -495,4 +481,85 @@ IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, "embedded-produce-crop-id-error"); } +// Suite of tests ensuring that only self-capture may crop, and that it may +// only crop to targets in its own tab, but that any target in its own tab +// is permitted. +class RegionCaptureSelfCaptureOnlyBrowserTest + : public RegionCaptureBrowserTest, + public WithParamInterface> { + public: + RegionCaptureSelfCaptureOnlyBrowserTest() + : capturing_entity_(std::get<0>(GetParam())), + self_capture_(std::get<1>(GetParam())), + target_element_tab_(std::get<2>(GetParam())), + target_frame_(std::get<3>(GetParam())) {} + ~RegionCaptureSelfCaptureOnlyBrowserTest() override = default; + + protected: + // The capture is done from kMainTab in all instances of this parameterized + // test. |capturing_entity_| controls whether the capture is initiated + // from the top-level document of said tab, or an embedded frame. + const Frame capturing_entity_; + + // Whether capturing self, or capturing the other tab. + const bool self_capture_; + + // Whether the element on whose crop-ID we'll call cropTo(): + // * |target_element_tab_| - whether it's in kMainTab or in kOtherTab. + // * |target_frame_| - whether it's in the top-level or an embedded frame. + const Tab target_element_tab_; + const Frame target_frame_; // Top-level or embedded frame. +}; + +std::string ParamsToString( + const TestParamInfo& + info) { + return base::StrCat( + {std::get<0>(info.param) == Frame::kTopLevelDocument ? "TopLevel" + : "EmbeddedFrame", + std::get<1>(info.param) ? "SelfCapturing" : "CapturingOtherTab", + "AndCroppingToElementIn", + std::get<2>(info.param) == kMainTab ? "OwnTabs" : "OtherTabs", + std::get<3>(info.param) == Frame::kTopLevelDocument ? "TopLevel" + : "EmbeddedFrame"}); +} + +INSTANTIATE_TEST_SUITE_P( + _, + RegionCaptureSelfCaptureOnlyBrowserTest, + Combine(Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame), + Bool(), + Values(kMainTab, kOtherTab), + Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame)), + ParamsToString); + +IN_PROC_BROWSER_TEST_P(RegionCaptureSelfCaptureOnlyBrowserTest, CropTo) { + SetUpTest(capturing_entity_, self_capture_); + + // Prevent test false-positive - ensure that both tabs participating in the + // test have at least one associated crop-ID, or otherwise they would not + // have a CropIdWebContentsHelper. + // To make things even clearer, ensure both the top-level and the embedded + // frame have produced crop-IDs. (This should not be necessary, but is + // done as an extra buffer against false-positives.) + tabs_[kMainTab].ProduceCropId(Frame::kTopLevelDocument); + tabs_[kMainTab].ProduceCropId(Frame::kEmbeddedFrame); + tabs_[kOtherTab].ProduceCropId(Frame::kTopLevelDocument); + tabs_[kOtherTab].ProduceCropId(Frame::kEmbeddedFrame); + + const std::string crop_id = + tabs_[target_element_tab_].ProduceCropId(target_frame_); + ASSERT_THAT(crop_id, IsValidCropId()); + + // Cropping only permitted if both conditions hold. + const bool expect_permitted = + (self_capture_ && target_element_tab_ == kMainTab); + + const std::string expected_result = base::StrCat( + {capturing_entity_ == Frame::kTopLevelDocument ? "top-level" : "embedded", + "-", expect_permitted ? "crop-success" : "crop-error"}); + + EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), expected_result); +} + #endif // !BUILDFLAG(IS_CHROMEOS_LACROS) diff --git a/src/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc b/src/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc index 880337c29a16f..a3e8095b7fac5 --- a/src/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc +++ b/src/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc @@ -1187,6 +1187,52 @@ IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, EXPECT_FALSE(frame_c_popup_opened); } +// Test that opening a window with `noopener` consumes user activation. +// crbug.com/1264543, crbug.com/1291210 +IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, + UserActivationConsumptionNoopener) { + // Start on a page a.com. + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + // Activate the frame by executing a dummy script. + const std::string no_op_script = "// No-op script"; + EXPECT_TRUE(ExecuteScript(web_contents, no_op_script)); + + // Add a popup observer. + content::TestNavigationObserver popup_observer(nullptr); + popup_observer.StartWatchingNewWebContents(); + + // Open a popup from the frame, with `noopener`. This should consume + // transient user activation. + GURL popup_url(embedded_test_server()->GetURL("popup.com", "/")); + EXPECT_TRUE(ExecuteScriptWithoutUserGesture( + web_contents, + base::StringPrintf( + "window.w = window.open('%s'+'title1.html', '_blank', 'noopener');", + popup_url.spec().c_str()))); + + // Try to open another popup. + EXPECT_TRUE(ExecuteScriptWithoutUserGesture( + web_contents, + base::StringPrintf( + "window.w = window.open('%s'+'title2.html', '_blank', 'noopener');", + popup_url.spec().c_str()))); + + // Wait and check that only one popup was opened. + popup_observer.Wait(); + EXPECT_EQ(2, browser()->tab_strip_model()->count()); + + content::WebContents* popup = + browser()->tab_strip_model()->GetActiveWebContents(); + EXPECT_EQ(embedded_test_server()->GetURL("popup.com", "/title1.html"), + popup->GetLastCommittedURL()); + EXPECT_NE(popup, web_contents); +} + // TODO(crbug.com/1021895): Flaky. // Tests that a cross-site iframe runs its beforeunload handler when closing a // tab. See https://crbug.com/853021. diff --git a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc index 659af40f838a2..e0b85b05e8dfe --- a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc +++ b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.cc @@ -129,16 +129,6 @@ int RecentlyUsedFoldersComboModel::GetDefaultIndex() const { return it == items_.end() ? 0 : static_cast(it - items_.begin()); } -void RecentlyUsedFoldersComboModel::AddObserver( - ui::ComboboxModelObserver* observer) { - observers_.AddObserver(observer); -} - -void RecentlyUsedFoldersComboModel::RemoveObserver( - ui::ComboboxModelObserver* observer) { - observers_.RemoveObserver(observer); -} - void RecentlyUsedFoldersComboModel::BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) {} @@ -175,7 +165,7 @@ void RecentlyUsedFoldersComboModel::OnWillRemoveBookmarks( } } if (changed) { - for (ui::ComboboxModelObserver& observer : observers_) + for (ui::ComboboxModelObserver& observer : observers()) observer.OnComboboxModelChanged(this); } } @@ -218,7 +208,7 @@ void RecentlyUsedFoldersComboModel::BookmarkAllUserNodesRemoved( } } if (changed) { - for (ui::ComboboxModelObserver& observer : observers_) + for (ui::ComboboxModelObserver& observer : observers()) observer.OnComboboxModelChanged(this); } } diff --git a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h index 8682e750f4407..0c02c685a0287 --- a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h +++ b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h @@ -38,8 +38,6 @@ class RecentlyUsedFoldersComboModel : public ui::ComboboxModel, std::u16string GetItemAt(int index) const override; bool IsItemSeparatorAt(int index) const override; int GetDefaultIndex() const override; - void AddObserver(ui::ComboboxModelObserver* observer) override; - void RemoveObserver(ui::ComboboxModelObserver* observer) override; // Overriden from bookmarks::BookmarkModelObserver: void BookmarkModelLoaded(bookmarks::BookmarkModel* model, @@ -90,8 +88,6 @@ class RecentlyUsedFoldersComboModel : public ui::ComboboxModel, const raw_ptr bookmark_model_; const raw_ptr parent_node_; - - base::ObserverList observers_; }; #endif // CHROME_BROWSER_UI_BOOKMARKS_RECENTLY_USED_FOLDERS_COMBO_MODEL_H_ diff --git a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc index 60455386b7d5f..3bfe6852b8a54 --- a/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc +++ b/src/chrome/browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc @@ -41,6 +41,8 @@ class TestComboboxModelObserver : public ui::ComboboxModelObserver { changed_ = true; } + void OnComboboxModelDestroying(ui::ComboboxModel* model) override {} + private: bool changed_; }; diff --git a/src/chrome/browser/ui/views/task_manager_view.cc b/src/chrome/browser/ui/views/task_manager_view.cc index 40590a63b0bec..6b7f5ad3651f3 --- a/src/chrome/browser/ui/views/task_manager_view.cc +++ b/src/chrome/browser/ui/views/task_manager_view.cc @@ -125,6 +125,12 @@ bool TaskManagerView::IsColumnVisible(int column_id) const { } void TaskManagerView::SetColumnVisibility(int column_id, bool new_visibility) { + // Check if there is at least 1 visible column before changing the visibility. + // If this column would be the last column to be visible and its hiding, then + // prevent this column visibility change. see crbug.com/1320307 for details. + if (!new_visibility && tab_table_->visible_columns().size() <= 1) + return; + tab_table_->SetColumnVisibility(column_id, new_visibility); } diff --git a/src/components/autofill/core/browser/ui/address_combobox_model.cc b/src/components/autofill/core/browser/ui/address_combobox_model.cc index 2f33fb3f5e12e..5e425ae742404 --- a/src/components/autofill/core/browser/ui/address_combobox_model.cc +++ b/src/components/autofill/core/browser/ui/address_combobox_model.cc @@ -83,14 +83,6 @@ int AddressComboboxModel::GetDefaultIndex() const { return ui::ComboboxModel::GetDefaultIndex(); } -void AddressComboboxModel::AddObserver(ui::ComboboxModelObserver* observer) { - observers_.AddObserver(observer); -} - -void AddressComboboxModel::RemoveObserver(ui::ComboboxModelObserver* observer) { - observers_.RemoveObserver(observer); -} - int AddressComboboxModel::AddNewProfile(const AutofillProfile& profile) { profiles_cache_.push_back(std::make_unique(profile)); UpdateAddresses(); @@ -130,7 +122,7 @@ void AddressComboboxModel::UpdateAddresses() { for (size_t i = 0; i < profiles_cache_.size(); ++i) addresses_.emplace_back(profiles_cache_[i]->guid(), labels[i]); - for (auto& observer : observers_) { + for (auto& observer : observers()) { observer.OnComboboxModelChanged(this); } } diff --git a/src/components/autofill/core/browser/ui/address_combobox_model.h b/src/components/autofill/core/browser/ui/address_combobox_model.h index 02d65209d2729..3a41b30c92543 --- a/src/components/autofill/core/browser/ui/address_combobox_model.h +++ b/src/components/autofill/core/browser/ui/address_combobox_model.h @@ -40,8 +40,6 @@ class AddressComboboxModel : public ui::ComboboxModel { std::u16string GetItemAt(int index) const override; bool IsItemSeparatorAt(int index) const override; int GetDefaultIndex() const override; - void AddObserver(ui::ComboboxModelObserver* observer) override; - void RemoveObserver(ui::ComboboxModelObserver* observer) override; // Adds |profile| to model and return its combobox index. The lifespan of // |profile| beyond this call is undefined so a copy must be made. @@ -72,9 +70,6 @@ class AddressComboboxModel : public ui::ComboboxModel { // If non empty, the guid of the address that should be selected by default. std::string default_selected_guid_; - - // To be called when the data for the given country code was loaded. - base::ObserverList observers_; }; } // namespace autofill diff --git a/src/components/autofill/core/browser/ui/region_combobox_model.cc b/src/components/autofill/core/browser/ui/region_combobox_model.cc index e17a040aeca51..b23c7b9eeb9a9 --- a/src/components/autofill/core/browser/ui/region_combobox_model.cc +++ b/src/components/autofill/core/browser/ui/region_combobox_model.cc @@ -66,14 +66,6 @@ bool RegionComboboxModel::IsItemSeparatorAt(int index) const { return regions_[index].first.empty(); } -void RegionComboboxModel::AddObserver(ui::ComboboxModelObserver* observer) { - observers_.AddObserver(observer); -} - -void RegionComboboxModel::RemoveObserver(ui::ComboboxModelObserver* observer) { - observers_.RemoveObserver(observer); -} - void RegionComboboxModel::OnRegionDataLoaded( const std::vector& regions) { // The RegionDataLoader will eventually self destruct after this call. @@ -94,7 +86,7 @@ void RegionComboboxModel::OnRegionDataLoaded( failed_to_load_data_ = true; } - for (auto& observer : observers_) { + for (auto& observer : observers()) { observer.OnComboboxModelChanged(this); } } diff --git a/src/components/autofill/core/browser/ui/region_combobox_model.h b/src/components/autofill/core/browser/ui/region_combobox_model.h index d1b5d8b9db0c4..52cffd5d00649 --- a/src/components/autofill/core/browser/ui/region_combobox_model.h +++ b/src/components/autofill/core/browser/ui/region_combobox_model.h @@ -54,8 +54,6 @@ class RegionComboboxModel : public ui::ComboboxModel { int GetItemCount() const override; std::u16string GetItemAt(int index) const override; bool IsItemSeparatorAt(int index) const override; - void AddObserver(ui::ComboboxModelObserver* observer) override; - void RemoveObserver(ui::ComboboxModelObserver* observer) override; private: // Callback for the RegionDataLoader. @@ -72,9 +70,6 @@ class RegionComboboxModel : public ui::ComboboxModel { // List of pairs for ADDRESS_HOME_STATE combobox values; std::vector> regions_; - // To be called when the data for the given country code was loaded. - base::ObserverList observers_; - // Weak pointer factory. base::WeakPtrFactory weak_factory_{this}; }; diff --git a/src/components/background_fetch/background_fetch_delegate_base.cc b/src/components/background_fetch/background_fetch_delegate_base.cc index d5971a94baa8a..1106781258375 --- a/src/components/background_fetch/background_fetch_delegate_base.cc +++ b/src/components/background_fetch/background_fetch_delegate_base.cc @@ -102,6 +102,7 @@ void BackgroundFetchDelegateBase::DownloadUrl( weak_ptr_factory_.GetWeakPtr()); params.traffic_annotation = net::MutableNetworkTrafficAnnotationTag(traffic_annotation); + params.request_params.update_first_party_url_on_redirect = false; JobDetails* job_details = GetJobDetails(job_id); if (job_details->job_state == JobDetails::State::kPendingWillStartPaused || @@ -171,8 +172,11 @@ void BackgroundFetchDelegateBase::CancelDownload(std::string job_id) { Abort(job_id); if (auto client = GetClient(job_id)) { + // The |download_guid| is not releavnt here as the job has already + // been aborted and is assumed to have been removed. client->OnJobCancelled( - job_id, blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI); + job_id, "" /* download_guid */, + blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI); } } @@ -242,14 +246,15 @@ void BackgroundFetchDelegateBase::MarkJobComplete(const std::string& job_id) { job_details->current_fetch_guids.clear(); } -void BackgroundFetchDelegateBase::FailFetch(const std::string& job_id) { +void BackgroundFetchDelegateBase::FailFetch(const std::string& job_id, + const std::string& download_guid) { // Save a copy before Abort() deletes the reference. const std::string unique_id = job_id; Abort(job_id); if (auto client = GetClient(unique_id)) { client->OnJobCancelled( - unique_id, + download_guid, unique_id, blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED); } } @@ -301,7 +306,7 @@ void BackgroundFetchDelegateBase::OnDownloadUpdated( // We only do this if total download size is specified. If not specified, // this check is skipped. This is to allow for situations when the // total download size cannot be known when invoking fetch. - FailFetch(job_id); + FailFetch(job_id, download_guid); return; } DoUpdateUi(job_id); diff --git a/src/components/background_fetch/background_fetch_delegate_base.h b/src/components/background_fetch/background_fetch_delegate_base.h index fe20adb475b77..b9d509d2fad8b --- a/src/components/background_fetch/background_fetch_delegate_base.h +++ b/src/components/background_fetch/background_fetch_delegate_base.h @@ -59,7 +59,7 @@ class BackgroundFetchDelegateBase : public content::BackgroundFetchDelegate { // Abort all ongoing downloads and fail the fetch. Currently only used when // the bytes downloaded exceed the total download size, if specified. - void FailFetch(const std::string& job_id); + void FailFetch(const std::string& job_id, const std::string& download_guid); void OnDownloadStarted( const std::string& guid, diff --git a/src/components/download/content/internal/download_driver_impl.cc b/src/components/download/content/internal/download_driver_impl.cc index d3c15b4852e80..ccff2b7ec99d2 --- a/src/components/download/content/internal/download_driver_impl.cc +++ b/src/components/download/content/internal/download_driver_impl.cc @@ -227,6 +227,8 @@ void DownloadDriverImpl::Start( download_url_params->set_isolation_info( request_params.isolation_info.value()); } + download_url_params->set_update_first_party_url_on_redirect( + request_params.update_first_party_url_on_redirect); download_manager_coordinator_->DownloadUrl(std::move(download_url_params)); } diff --git a/src/components/download/internal/common/download_utils.cc b/src/components/download/internal/common/download_utils.cc index 73f6d375ef2f6..499e4f1aea472 --- a/src/components/download/internal/common/download_utils.cc +++ b/src/components/download/internal/common/download_utils.cc @@ -349,8 +349,10 @@ std::unique_ptr CreateResourceRequest( // cross-site URL has been visited before. url::Origin origin = url::Origin::Create(params->url()); request->trusted_params->isolation_info = net::IsolationInfo::Create( - net::IsolationInfo::RequestType::kMainFrame, origin, origin, - net::SiteForCookies::FromOrigin(origin)); + params->update_first_party_url_on_redirect() + ? net::IsolationInfo::RequestType::kMainFrame + : net::IsolationInfo::RequestType::kOther, + origin, origin, net::SiteForCookies::FromOrigin(origin)); request->site_for_cookies = net::SiteForCookies::FromUrl(params->url()); } @@ -358,7 +360,8 @@ std::unique_ptr CreateResourceRequest( request->referrer = params->referrer(); request->referrer_policy = params->referrer_policy(); request->is_main_frame = true; - request->update_first_party_url_on_redirect = true; + request->update_first_party_url_on_redirect = + params->update_first_party_url_on_redirect(); // Downloads should be treated as navigations from Fetch spec perspective. // See also: diff --git a/src/components/download/public/background_service/download_params.h b/src/components/download/public/background_service/download_params.h index e567fcc8aea30..ce6070e67bbfc --- a/src/components/download/public/background_service/download_params.h +++ b/src/components/download/public/background_service/download_params.h @@ -120,6 +120,12 @@ struct RequestParams { // The isolation info of the request, this won't be persisted to db and will // be invalidate during download resumption in new browser session. absl::optional isolation_info; + + // First-party URL redirect policy: During server redirects, whether the + // first-party URL for cookies will need to be changed. Download is normally + // considered a main frame navigation. However, this is not true for + // background fetch. + bool update_first_party_url_on_redirect = true; }; // The parameters that describe a download request made to the DownloadService. diff --git a/src/components/download/public/common/download_url_parameters.cc b/src/components/download/public/common/download_url_parameters.cc index 3dec7148d86cd..25ea6f13e0df9 --- a/src/components/download/public/common/download_url_parameters.cc +++ b/src/components/download/public/common/download_url_parameters.cc @@ -34,7 +34,8 @@ DownloadUrlParameters::DownloadUrlParameters( traffic_annotation_(traffic_annotation), download_source_(DownloadSource::UNKNOWN), require_safety_checks_(true), - has_user_gesture_(false) {} + has_user_gesture_(false), + update_first_party_url_on_redirect_(true) {} DownloadUrlParameters::~DownloadUrlParameters() = default; diff --git a/src/components/download/public/common/download_url_parameters.h b/src/components/download/public/common/download_url_parameters.h index ba0a03cb035a7..61eb9af8a00c3 --- a/src/components/download/public/common/download_url_parameters.h +++ b/src/components/download/public/common/download_url_parameters.h @@ -279,6 +279,11 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadUrlParameters { has_user_gesture_ = has_user_gesture; } + void set_update_first_party_url_on_redirect( + bool update_first_party_url_on_redirect) { + update_first_party_url_on_redirect_ = update_first_party_url_on_redirect; + } + OnStartedCallback& callback() { return callback_; } bool content_initiated() const { return content_initiated_; } const std::string& last_modified() const { return last_modified_; } @@ -335,6 +340,9 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadUrlParameters { return isolation_info_; } bool has_user_gesture() const { return has_user_gesture_; } + bool update_first_party_url_on_redirect() const { + return update_first_party_url_on_redirect_; + } // STATE CHANGING: All save_info_ sub-objects will be in an indeterminate // state following this call. @@ -383,6 +391,7 @@ class COMPONENTS_DOWNLOAD_EXPORT DownloadUrlParameters { bool require_safety_checks_; absl::optional isolation_info_; bool has_user_gesture_; + bool update_first_party_url_on_redirect_; }; } // namespace download diff --git a/src/components/history/core/browser/history_backend.cc b/src/components/history/core/browser/history_backend.cc index 05215a1c8d774..6f919fc3266c2 --- a/src/components/history/core/browser/history_backend.cc +++ b/src/components/history/core/browser/history_backend.cc @@ -287,6 +287,15 @@ HistoryBackend::~HistoryBackend() { // Release stashed embedder object before cleaning up the databases. supports_user_data_helper_.reset(); + // Clear the error callback. The error callback that is installed does not + // process an error immediately, rather it uses a PostTask() with `this`. As + // `this` is being deleted, scheduling a PostTask() with `this` would be + // fatal (use-after-free). Additionally, as we're in shutdown, there isn't + // much point in trying to handle the error. If the error is really fatal, + // we'll cleanup the next time the backend is created. + if (db_) + db_->reset_error_callback(); + // First close the databases before optionally running the "destroy" task. CloseAllDatabases(); diff --git a/src/components/history/core/browser/history_database.h b/src/components/history/core/browser/history_database.h index 10249f68dcb8a..335672d31aea8 --- a/src/components/history/core/browser/history_database.h +++ b/src/components/history/core/browser/history_database.h @@ -80,6 +80,7 @@ class HistoryDatabase : public DownloadDatabase, void set_error_callback(const sql::Database::ErrorCallback& error_callback) { db_.set_error_callback(error_callback); } + void reset_error_callback() { db_.reset_error_callback(); } // Must call this function to complete initialization. Will return // sql::INIT_OK on success. Otherwise, no other function should be called. You diff --git a/src/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc b/src/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc index a5af234feebb1..fe620aad1198b --- a/src/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc +++ b/src/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc @@ -315,8 +315,8 @@ void RealTimeUrlLookupServiceBase::MayBeCacheRealTimeUrlVerdict( if (response.threat_info_size() > 0) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&VerdictCacheManager::CacheRealTimeUrlVerdict, - base::Unretained(cache_manager_), url, - response, base::Time::Now())); + cache_manager_->GetWeakPtr(), url, response, + base::Time::Now())); } } diff --git a/src/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc b/src/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc index 7f1018beeaae3..72c459fabddd1 --- a/src/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc +++ b/src/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc @@ -136,7 +136,9 @@ class RealTimeUrlLookupServiceTest : public PlatformTest { void TearDown() override { cache_manager_.reset(); - content_setting_map_->ShutdownOnUIThread(); + if (content_setting_map_) { + content_setting_map_->ShutdownOnUIThread(); + } rt_service_->Shutdown(); } @@ -1139,6 +1141,24 @@ TEST_F(RealTimeUrlLookupServiceTest, TestShutdown_CallbackNotPostedOnShutdown) { task_environment_.RunUntilIdle(); } +TEST_F(RealTimeUrlLookupServiceTest, TestShutdown_CacheManagerReset) { + GURL url("https://a.example.test/path1/path2"); + // Post a task to cache_manager_ to cache the verdict. + MayBeCacheRealTimeUrlVerdict(url, RTLookupResponse::ThreatInfo::DANGEROUS, + RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING, + 60, "a.example.test/path1/path2", + RTLookupResponse::ThreatInfo::COVERING_MATCH); + + // Shutdown and delete depending objects. + rt_service()->Shutdown(); + cache_manager_.reset(); + content_setting_map_->ShutdownOnUIThread(); + content_setting_map_.reset(); + + // The task to cache_manager_ should be cancelled and not cause crash. + task_environment_.RunUntilIdle(); +} + TEST_F(RealTimeUrlLookupServiceTest, TestShutdown_SendRequestNotCalledOnShutdown) { // Never send the request if shutdown is triggered before OnGetAccessToken(). diff --git a/src/components/viz/service/display/skia_renderer.cc b/src/components/viz/service/display/skia_renderer.cc index ca66736a05005..259a8fc2c4515 --- a/src/components/viz/service/display/skia_renderer.cc +++ b/src/components/viz/service/display/skia_renderer.cc @@ -1426,34 +1426,20 @@ void SkiaRenderer::DrawQuadParams::ApplyScissor( // device space, it will be contained in in the original scissor. // Applying the scissor explicitly means avoiding a clipRect() call and // allows more quads to be batched together in a DrawEdgeAAImageSet call - float left_inset = local_scissor.x() - visible_rect.x(); - float top_inset = local_scissor.y() - visible_rect.y(); - float right_inset = visible_rect.right() - local_scissor.right(); - float bottom_inset = visible_rect.bottom() - local_scissor.bottom(); + float x_epsilon = kAAEpsilon / content_device_transform.matrix().get(0, 0); + float y_epsilon = kAAEpsilon / content_device_transform.matrix().get(1, 1); - // The scissor is a non-AA clip, so we unset the bit flag for clipped edges. - if (left_inset >= kAAEpsilon) { + // The scissor is a non-AA clip, so unset the bit flag for clipped edges. + if (local_scissor.x() - visible_rect.x() >= x_epsilon) aa_flags &= ~SkCanvas::kLeft_QuadAAFlag; - } else { - left_inset = 0; - } - if (top_inset >= kAAEpsilon) { + if (local_scissor.y() - visible_rect.y() >= y_epsilon) aa_flags &= ~SkCanvas::kTop_QuadAAFlag; - } else { - top_inset = 0; - } - if (right_inset >= kAAEpsilon) { + if (visible_rect.right() - local_scissor.right() >= x_epsilon) aa_flags &= ~SkCanvas::kRight_QuadAAFlag; - } else { - right_inset = 0; - } - if (bottom_inset >= kAAEpsilon) { + if (visible_rect.bottom() - local_scissor.bottom() >= y_epsilon) aa_flags &= ~SkCanvas::kBottom_QuadAAFlag; - } else { - bottom_inset = 0; - } - visible_rect.Inset(left_inset, top_inset, right_inset, bottom_inset); + visible_rect.Intersect(local_scissor); vis_tex_coords = visible_rect; scissor_rect.reset(); } diff --git a/src/content/browser/back_forward_cache_basics_browsertest.cc b/src/content/browser/back_forward_cache_basics_browsertest.cc index ed38b32cf9ff9..b2ff831b57454 --- a/src/content/browser/back_forward_cache_basics_browsertest.cc +++ b/src/content/browser/back_forward_cache_basics_browsertest.cc @@ -32,6 +32,8 @@ using testing::UnorderedElementsAreArray; namespace content { +using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason; + // Navigate from A to B and go back. IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) { ASSERT_TRUE(embedded_test_server()->Start()); @@ -742,8 +744,8 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, web_contents()->GetController().GoBack(); EXPECT_FALSE(WaitForLoadStop(shell()->web_contents())); ExpectNotRestored( - {BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK, - BackForwardCacheMetrics::NotRestoredReason::kNoResponseHead}, + {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead, + NotRestoredReason::kErrorDocument}, {}, {}, {}, {}, FROM_HERE); } diff --git a/src/content/browser/back_forward_cache_browsertest.cc b/src/content/browser/back_forward_cache_browsertest.cc index 7f6fca06d92ae..9ec8a4b853498 --- a/src/content/browser/back_forward_cache_browsertest.cc +++ b/src/content/browser/back_forward_cache_browsertest.cc @@ -52,6 +52,7 @@ #include "content/public/test/test_navigation_throttle_inserter.h" #include "content/public/test/test_utils.h" #include "content/public/test/text_input_test_utils.h" +#include "content/public/test/url_loader_interceptor.h" #include "content/shell/browser/shell.h" #include "content/shell/browser/shell_javascript_dialog_manager.h" #include "content/test/content_browser_test_utils_internal.h" @@ -670,6 +671,22 @@ void BackForwardCacheBrowserTest::ExpectBrowsingInstanceNotSwappedReasons( << location.ToString(); } +void BackForwardCacheBrowserTest::NavigateAndBlock(GURL url, + int history_offset) { + // Block the navigation with an error. + std::unique_ptr url_interceptor = + URLLoaderInterceptor::SetupRequestFailForURL(url, + net::ERR_BLOCKED_BY_CLIENT); + if (history_offset) { + shell()->GoBackOrForward(history_offset); + } else { + shell()->LoadURL(url); + } + WaitForLoadStop(web_contents()); + ASSERT_EQ(current_frame_host()->GetLastCommittedURL(), url); + ASSERT_TRUE(current_frame_host()->IsErrorDocument()); +} + std::initializer_list Elements( std::initializer_list t) { return t; diff --git a/src/content/browser/back_forward_cache_browsertest.h b/src/content/browser/back_forward_cache_browsertest.h index 4d7e5baafc8bf..476e4882c12dc --- a/src/content/browser/back_forward_cache_browsertest.h +++ b/src/content/browser/back_forward_cache_browsertest.h @@ -167,6 +167,11 @@ class BackForwardCacheBrowserTest : public ContentBrowserTest, void ReleaseKeyboardLock(RenderFrameHostImpl* rfh); + // Start a navigation to |url| but block it on an error. If |history_offset| + // is not 0, then the navigation will be a history navigation and this will + // assert that the URL after navigation is |url|. + void NavigateAndBlock(GURL url, int history_offset); + base::HistogramTester histogram_tester_; bool same_site_back_forward_cache_enabled_ = true; diff --git a/src/content/browser/back_forward_cache_internal_browsertest.cc b/src/content/browser/back_forward_cache_internal_browsertest.cc index 7264c2e59c9d3..955cf63008447 --- a/src/content/browser/back_forward_cache_internal_browsertest.cc +++ b/src/content/browser/back_forward_cache_internal_browsertest.cc @@ -49,6 +49,8 @@ using ::testing::UnorderedElementsAreArray; namespace content { +using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason; + // Ensure flushing the BackForwardCache works properly. IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) { ASSERT_TRUE(embedded_test_server()->Start()); @@ -3671,4 +3673,69 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); } +// Test that when we navigate away from an error page and back with no error +// that we don't serve the error page from BFCache. +IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, + ErrorDocumentNotCachedWithSecondError) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); + + // Navigate to a.com. + ASSERT_TRUE(NavigateToURL(web_contents(), url_a)); + + // Navigate to b.com and block due to an error. + NavigateAndBlock(url_b, /*history_offset=*/0); + RenderFrameHostImplWrapper rfh_b(current_frame_host()); + + // Navigate back to a.com. + ASSERT_TRUE(HistoryGoBack(web_contents())); + ExpectRestored(FROM_HERE); + ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); + + // Navigate forward to b.com again and block with an error again. + NavigateAndBlock(url_b, /*history_offset=*/1); + ExpectNotRestored( + {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead, + NotRestoredReason::kErrorDocument}, + {}, {}, {}, {}, FROM_HERE); +} + +// Test that when we navigate away from an error page and back with no error +// that we don't serve the error page from BFCache. +IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, + ErrorDocumentNotCachedWithoutSecondError) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); + GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); + + // Navigate to a.com. + ASSERT_TRUE(NavigateToURL(web_contents(), url_a)); + + // Navigate to b.com and block due to an error. + NavigateAndBlock(url_b, /*history_offset=*/0); + RenderFrameHostImplWrapper rfh_b(current_frame_host()); + + int history_entry_id = + web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID(); + + // Navigate back to a.com. + ASSERT_TRUE(HistoryGoBack(web_contents())); + ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); + + // Navigate forward to b.com again with no error. + ASSERT_TRUE(HistoryGoForward(web_contents())); + + // We would normally confirm that the blocking reasons are correct, however, + // when performing a history navigations back to an error document, a new + // entry is created and the reasons in the old entry are not recorded. + // + // Check that we indeed got a new history entry. + ASSERT_NE( + history_entry_id, + web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID()); +} + } // namespace content diff --git a/src/content/browser/background_fetch/background_fetch_delegate_proxy.cc b/src/content/browser/background_fetch/background_fetch_delegate_proxy.cc index e50cda87fceb9..a6d8b01f7e703 --- a/src/content/browser/background_fetch/background_fetch_delegate_proxy.cc +++ b/src/content/browser/background_fetch/background_fetch_delegate_proxy.cc @@ -220,6 +220,7 @@ void BackgroundFetchDelegateProxy::MarkJobComplete( void BackgroundFetchDelegateProxy::OnJobCancelled( const std::string& job_unique_id, + const std::string& download_guid, blink::mojom::BackgroundFetchFailureReason reason_to_abort) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK( @@ -232,8 +233,20 @@ void BackgroundFetchDelegateProxy::OnJobCancelled( if (it == controller_map_.end()) return; - if (const auto& controller = it->second) + if (const auto& controller = it->second) { + if (reason_to_abort == + blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED) { + // Mark the request as complete and failed to avoid leaking information + // about the size of the resource. + controller->DidCompleteRequest( + download_guid, + std::make_unique( + nullptr /* response */, base::Time::Now(), + BackgroundFetchResult::FailureReason::FETCH_ERROR)); + } + controller->AbortFromDelegate(reason_to_abort); + } } void BackgroundFetchDelegateProxy::OnDownloadStarted( diff --git a/src/content/browser/background_fetch/background_fetch_delegate_proxy.h b/src/content/browser/background_fetch/background_fetch_delegate_proxy.h index ad821c2ee9054..2c24134be1ebf --- a/src/content/browser/background_fetch/background_fetch_delegate_proxy.h +++ b/src/content/browser/background_fetch/background_fetch_delegate_proxy.h @@ -142,6 +142,7 @@ class CONTENT_EXPORT BackgroundFetchDelegateProxy // BackgroundFetchDelegate::Client implementation: void OnJobCancelled( const std::string& job_unique_id, + const std::string& download_guid, blink::mojom::BackgroundFetchFailureReason reason_to_abort) override; void OnDownloadComplete( const std::string& job_unique_id, diff --git a/src/content/browser/background_fetch/storage/mark_request_complete_task.cc b/src/content/browser/background_fetch/storage/mark_request_complete_task.cc index 9036ec9a92212..76cc94dd0ea8f --- a/src/content/browser/background_fetch/storage/mark_request_complete_task.cc +++ b/src/content/browser/background_fetch/storage/mark_request_complete_task.cc @@ -103,6 +103,8 @@ void MarkRequestCompleteTask::StoreResponse(base::OnceClosure done_closure) { BackgroundFetchCrossOriginFilter filter( registration_id_.storage_key().origin(), *request_info_); if (!filter.CanPopulateBody()) { + // Don't expose the initial URL in case of cross-origin redirects. + response_->url_list.resize(1); failure_reason_ = proto::BackgroundFetchRegistration::FETCH_ERROR; // No point writing the response to the cache since it won't be exposed. CreateAndStoreCompletedRequest(std::move(done_closure)); diff --git a/src/content/browser/cross_origin_opener_policy_browsertest.cc b/src/content/browser/cross_origin_opener_policy_browsertest.cc index 14989bfafb34a..07a7c403b96aa --- a/src/content/browser/cross_origin_opener_policy_browsertest.cc +++ b/src/content/browser/cross_origin_opener_policy_browsertest.cc @@ -11,6 +11,7 @@ #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/content_navigation_policy.h" +#include "content/public/browser/site_isolation_policy.h" #include "content/public/common/content_features.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" @@ -3337,6 +3338,104 @@ IN_PROC_BROWSER_TEST_P(CrossOriginOpenerPolicyBrowserTest, )")); } +// This test is a reproducer for https://crbug.com/1305394. +IN_PROC_BROWSER_TEST_P(CrossOriginOpenerPolicyBrowserTest, + CrossOriginIframeCoopBypass) { + // This test requires that a cross-origin iframe be placed in its own + // process. It is irrelevant without strict site isolation. + if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites()) + return; + + GURL non_coop_page(https_server()->GetURL("a.test", "/title1.html")); + GURL cross_origin_non_coop_page( + https_server()->GetURL("b.test", "/title1.html")); + GURL coop_page(https_server()->GetURL( + "a.test", "/set-header?cross-origin-opener-policy: same-origin")); + + // Get an initial non-COOP page with an empty popup. + EXPECT_TRUE(NavigateToURL(shell(), non_coop_page)); + RenderFrameHostImpl* initial_main_rfh = current_frame_host(); + + ShellAddedObserver shell_observer; + EXPECT_TRUE( + ExecJs(initial_main_rfh, JsReplace("window.open($1)", non_coop_page))); + WebContentsImpl* popup = + static_cast(shell_observer.GetShell()->web_contents()); + RenderFrameHostImpl* popup_rfh = popup->GetMainFrame(); + + // At this stage we have a single SiteInstance used both for the main page and + // the same-site popup. + SiteInstanceImpl* initial_main_si = initial_main_rfh->GetSiteInstance(); + SiteInstanceImpl* popup_si = popup_rfh->GetSiteInstance(); + EXPECT_EQ(initial_main_si, popup_si); + RenderProcessHost* process_A = initial_main_si->GetProcess(); + + // The popup then navigates the opener to a COOP page. + EXPECT_TRUE(ExecJs(popup_rfh, JsReplace("opener.location = $1", coop_page))); + EXPECT_TRUE(WaitForLoadStop(web_contents())); + + // This should trigger a BrowsingInstance swap. The main frame gets a new + // unrelated BrowsingInstance, and clears the opener. + // Note: We need to wait for the RenderView deletion to be propagated in the + // renderer for window.opener to be cleared. To avoid flakes, we check the + // opener at the end of this test. + RenderFrameHostImpl* main_rfh = current_frame_host(); + SiteInstanceImpl* main_si = main_rfh->GetSiteInstance(); + RenderProcessHost* process_B = main_si->GetProcess(); + EXPECT_FALSE(popup_si->IsRelatedSiteInstance(main_si)); + + // The popup still uses process A, but the main page now uses a different + // process. No proxy should remain between the two site instances as the + // opener link has been cut. + EXPECT_EQ(process_A, popup_si->GetProcess()); + EXPECT_NE(process_B, process_A); + EXPECT_TRUE(popup_rfh->frame_tree_node() + ->render_manager() + ->GetAllProxyHostsForTesting() + .empty()); + EXPECT_TRUE(main_rfh->frame_tree_node() + ->render_manager() + ->GetAllProxyHostsForTesting() + .empty()); + + // Load an iframe that is cross-origin to the top frame's opener. + ASSERT_TRUE(ExecJs(popup_rfh, JsReplace(R"( + const frame = document.createElement('iframe'); + frame.src = $1; + document.body.appendChild(frame); + )", + cross_origin_non_coop_page))); + EXPECT_TRUE(WaitForLoadStop(popup)); + RenderFrameHostImpl* iframe_rfh = + popup_rfh->child_at(0)->current_frame_host(); + SiteInstanceImpl* iframe_si = iframe_rfh->GetSiteInstance(); + + // The iframe being cross-origin, it is put in a different but related + // SiteInstance. + EXPECT_TRUE(iframe_si->IsRelatedSiteInstance(popup_si)); + EXPECT_FALSE(iframe_si->IsRelatedSiteInstance(main_si)); + + // We end up with the main window, the main popup frame and the iframe all + // living in their own process. We should only have proxies from the popup + // main frame to iframe and vice versa. Opener links should stay severed. + RenderProcessHost* process_C = iframe_si->GetProcess(); + EXPECT_NE(process_C, process_A); + EXPECT_NE(process_C, process_B); + EXPECT_EQ(1u, iframe_rfh->frame_tree_node() + ->render_manager() + ->GetAllProxyHostsForTesting() + .size()); + EXPECT_EQ(1u, popup_rfh->frame_tree_node() + ->render_manager() + ->GetAllProxyHostsForTesting() + .size()); + + // The opener should not be reachable either from the popup main frame nor the + // popup iframe. + EXPECT_EQ(true, EvalJs(popup_rfh, "opener == null")); + EXPECT_EQ(true, EvalJs(iframe_rfh, "parent.opener == null")); +} + // TODO(https://crbug.com/1101339). Test inheritance of the virtual browsing // context group when using window.open from an iframe, same-origin and // cross-origin. diff --git a/src/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/src/content/browser/devtools/protocol/devtools_protocol_browsertest.cc index 4c15051c418b3..7d37e65a5ceb2 --- a/src/content/browser/devtools/protocol/devtools_protocol_browsertest.cc +++ b/src/content/browser/devtools/protocol/devtools_protocol_browsertest.cc @@ -408,7 +408,8 @@ class CaptureScreenshotTest : public DevToolsProtocolTest { bool from_surface, const gfx::RectF& clip = gfx::RectF(), float clip_scale = 0, - bool capture_beyond_viewport = false) { + bool capture_beyond_viewport = false, + bool expect_error = false) { std::unique_ptr params(new base::DictionaryValue()); params->SetStringKey("format", EncodingEnumToString(encoding)); params->SetIntKey("quality", 100); @@ -427,18 +428,24 @@ class CaptureScreenshotTest : public DevToolsProtocolTest { } SendCommand("Page.captureScreenshot", std::move(params)); - std::string base64; - EXPECT_TRUE(result_->GetString("data", &base64)); std::unique_ptr result_bitmap; - if (encoding == ScreenshotEncoding::PNG) { - result_bitmap = std::make_unique(); - EXPECT_TRUE(DecodePNG(base64, result_bitmap.get())); - } else if (encoding == ScreenshotEncoding::JPEG) { - result_bitmap = DecodeJPEG(base64); + if (expect_error) { + EXPECT_THAT(error_, base::test::DictionaryHasValue( + "code", base::Value(static_cast( + crdtp::DispatchCode::SERVER_ERROR)))); } else { - // Decode not implemented. + std::string base64; + EXPECT_TRUE(result_->GetString("data", &base64)); + if (encoding == ScreenshotEncoding::PNG) { + result_bitmap = std::make_unique(); + EXPECT_TRUE(DecodePNG(base64, result_bitmap.get())); + } else if (encoding == ScreenshotEncoding::JPEG) { + result_bitmap = DecodeJPEG(base64); + } else { + // Decode not implemented. + } + EXPECT_TRUE(result_bitmap); } - EXPECT_TRUE(result_bitmap); return result_bitmap; } @@ -538,6 +545,10 @@ class CaptureScreenshotTest : public DevToolsProtocolTest { SendCommand("Emulation.clearDeviceMetricsOverride", nullptr); } + bool MayAttachToBrowser() override { return may_attach_to_browser_; } + + bool may_attach_to_browser_ = true; + private: #if !BUILDFLAG(IS_ANDROID) void SetUp() override { @@ -839,6 +850,17 @@ IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, TransparentScreenshots) { #endif // !BUILDFLAG(IS_ANDROID) } +IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest, + OnlyScreenshotsFromSurfaceWhenUnsafeNotAllowed) { + may_attach_to_browser_ = false; + shell()->LoadURL(GURL("about:blank")); + EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); + Attach(); + + CaptureScreenshot(ScreenshotEncoding::PNG, false, gfx::RectF(), 0, true, + true); +} + #if BUILDFLAG(IS_ANDROID) // Disabled, see http://crbug.com/469947. IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, DISABLED_SynthesizePinchGesture) { diff --git a/src/content/browser/devtools/protocol/page_handler.cc b/src/content/browser/devtools/protocol/page_handler.cc index 863f598c1f431..648b19779179b --- a/src/content/browser/devtools/protocol/page_handler.cc +++ b/src/content/browser/devtools/protocol/page_handler.cc @@ -198,9 +198,12 @@ PageHandler::PageHandler( EmulationHandler* emulation_handler, BrowserHandler* browser_handler, bool allow_unsafe_operations, + bool may_capture_screenshots_not_from_surface, absl::optional navigation_initiator_origin) : DevToolsDomainHandler(Page::Metainfo::domainName), allow_unsafe_operations_(allow_unsafe_operations), + may_capture_screenshots_not_from_surface_( + may_capture_screenshots_not_from_surface), navigation_initiator_origin_(navigation_initiator_origin), enabled_(false), screencast_enabled_(false), @@ -759,6 +762,11 @@ void PageHandler::CaptureScreenshot( // We don't support clip/emulation when capturing from window, bail out. if (!from_surface.fromMaybe(true)) { + if (!may_capture_screenshots_not_from_surface_) { + callback->sendFailure( + Response::ServerError("Only screenshots from surface are allowed.")); + return; + } widget_host->GetSnapshotFromBrowser( base::BindOnce(&PageHandler::ScreenshotCaptured, weak_factory_.GetWeakPtr(), std::move(callback), @@ -1419,6 +1427,8 @@ Page::BackForwardCacheNotRestoredReason NotRestoredReasonToProtocol( case Reason::kActivationNavigationsDisallowedForBug1234857: return Page::BackForwardCacheNotRestoredReasonEnum:: ActivationNavigationsDisallowedForBug1234857; + case Reason::kErrorDocument: + return Page::BackForwardCacheNotRestoredReasonEnum::ErrorDocument; case Reason::kBlocklistedFeatures: // Blocklisted features should be handled separately and be broken down // into sub reasons. @@ -1698,6 +1708,7 @@ Page::BackForwardCacheNotRestoredReasonType MapNotRestoredReasonToType( case Reason::kCacheControlNoStoreCookieModified: case Reason::kCacheControlNoStoreHTTPOnlyCookieModified: case Reason::kNoResponseHead: + case Reason::kErrorDocument: return Page::BackForwardCacheNotRestoredReasonTypeEnum::Circumstantial; case Reason::kOptInUnloadHeaderNotPresent: case Reason::kUnloadHandlerExistsInMainFrame: diff --git a/src/content/browser/devtools/protocol/page_handler.h b/src/content/browser/devtools/protocol/page_handler.h index 7a2186a1530b3..d26ae7a1e33ad --- a/src/content/browser/devtools/protocol/page_handler.h +++ b/src/content/browser/devtools/protocol/page_handler.h @@ -65,6 +65,7 @@ class PageHandler : public DevToolsDomainHandler, PageHandler(EmulationHandler* emulation_handler, BrowserHandler* browser_handler, bool allow_unsafe_operations, + bool may_capture_screenshots_not_from_surface, absl::optional navigation_initiator_origin); PageHandler(const PageHandler&) = delete; @@ -212,6 +213,7 @@ class PageHandler : public DevToolsDomainHandler, void OnDownloadDestroyed(download::DownloadItem* item) override; const bool allow_unsafe_operations_; + const bool may_capture_screenshots_not_from_surface_; const absl::optional navigation_initiator_origin_; bool enabled_; diff --git a/src/content/browser/devtools/render_frame_devtools_agent_host.cc b/src/content/browser/devtools/render_frame_devtools_agent_host.cc index 412b3550a73a2..2200e60394b06 --- a/src/content/browser/devtools/render_frame_devtools_agent_host.cc +++ b/src/content/browser/devtools/render_frame_devtools_agent_host.cc @@ -357,6 +357,7 @@ bool RenderFrameDevToolsAgentHost::AttachSession(DevToolsSession* session, session->AddHandler(std::make_unique( emulation_handler_ptr, browser_handler_ptr, session->GetClient()->AllowUnsafeOperations(), + session->GetClient()->MayAttachToBrowser(), session->GetClient()->GetNavigationInitiatorOrigin())); session->AddHandler(std::make_unique()); if (!frame_tree_node_ || !frame_tree_node_->parent()) { diff --git a/src/content/browser/download/download_browsertest.cc b/src/content/browser/download/download_browsertest.cc index 3ffdc630a10ce..b22c972d1ac3a --- a/src/content/browser/download/download_browsertest.cc +++ b/src/content/browser/download/download_browsertest.cc @@ -3735,6 +3735,58 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, UpdateSiteForCookies) { site_a.GetURL("a.test", "/"))); } +// Tests that if `update_first_party_url_on_redirect` is set to false, download +// will not behave like a top-level frame navigation and SameSite=Strict cookies +// will not be set on a redirection. +IN_PROC_BROWSER_TEST_F( + DownloadContentTest, + SiteForCookies_DownloadUrl_NotUpdateFirstPartyUrlOnRedirect) { + net::EmbeddedTestServer site_a; + net::EmbeddedTestServer site_b; + + base::StringPairs cookie_headers; + cookie_headers.push_back(std::make_pair( + std::string("Set-Cookie"), std::string("A=strict; SameSite=Strict"))); + cookie_headers.push_back(std::make_pair(std::string("Set-Cookie"), + std::string("B=lax; SameSite=Lax"))); + + // This will request a URL on b.test, which redirects to a url that sets the + // cookies on a.test. + site_a.RegisterRequestHandler(CreateBasicResponseHandler( + "/sets-samesite-cookies", net::HTTP_OK, cookie_headers, + "application/octet-stream", "abcd")); + ASSERT_TRUE(site_a.Start()); + site_b.RegisterRequestHandler( + CreateRedirectHandler("/redirected-download", + site_a.GetURL("a.test", "/sets-samesite-cookies"))); + ASSERT_TRUE(site_b.Start()); + + // Download the file. + SetupEnsureNoPendingDownloads(); + std::unique_ptr download_parameters( + DownloadRequestUtils::CreateDownloadForWebContentsMainFrame( + shell()->web_contents(), + site_b.GetURL("b.test", "/redirected-download"), + TRAFFIC_ANNOTATION_FOR_TESTS)); + download_parameters->set_update_first_party_url_on_redirect(false); + std::unique_ptr observer(CreateWaiter(shell(), 1)); + DownloadManagerForShell(shell())->DownloadUrl(std::move(download_parameters)); + observer->WaitForFinished(); + + // Get the important info from other threads and check it. + EXPECT_TRUE(EnsureNoPendingDownloads()); + + std::vector downloads; + DownloadManagerForShell(shell())->GetAllDownloads(&downloads); + ASSERT_EQ(1u, downloads.size()); + ASSERT_EQ(download::DownloadItem::COMPLETE, downloads[0]->GetState()); + + // Check that the cookies were not set on a.test. + EXPECT_EQ("", + content::GetCookies(shell()->web_contents()->GetBrowserContext(), + site_a.GetURL("a.test", "/"))); +} + // Verifies that isolation info set in DownloadUrlParameters can be populated. IN_PROC_BROWSER_TEST_F(DownloadContentTest, SiteForCookies_DownloadUrl_IsolationInfoPopulated) { diff --git a/src/content/browser/indexed_db/indexed_db_context_impl.cc b/src/content/browser/indexed_db/indexed_db_context_impl.cc index 24577f1069e1c..bb86bba0f325c --- a/src/content/browser/indexed_db/indexed_db_context_impl.cc +++ b/src/content/browser/indexed_db/indexed_db_context_impl.cc @@ -120,7 +120,7 @@ void GetAllStorageKeysAndPaths(const base::FilePath& indexeddb_path, // static void IndexedDBContextImpl::ReleaseOnIDBSequence( scoped_refptr&& context) { - if (!context->idb_task_runner_->RunsTasksInCurrentSequence()) { + if (!context->IDBTaskRunner()->RunsTasksInCurrentSequence()) { IndexedDBContextImpl* context_ptr = context.get(); context_ptr->IDBTaskRunner()->ReleaseSoon(FROM_HERE, std::move(context)); } @@ -168,37 +168,32 @@ IndexedDBContextImpl::IndexedDBContextImpl( storage::QuotaClientType::kIndexedDatabase, {blink::mojom::StorageType::kTemporary}); - // This is safe because the IndexedDBContextImpl must be destructed on the - // IDBTaskRunner, and this task will always happen before that. - idb_task_runner_->PostTask( - FROM_HERE, - base::BindOnce( - [](mojo::Remote* - blob_storage_context, - mojo::Remote* - file_system_access_context, - mojo::Receiver* quota_client_receiver, - mojo::PendingRemote - pending_blob_storage_context, - mojo::PendingRemote - pending_file_system_access_context, - mojo::PendingReceiver - quota_client_pending_receiver) { - quota_client_receiver->Bind( - std::move(quota_client_pending_receiver)); - if (pending_blob_storage_context) { - blob_storage_context->Bind( - std::move(pending_blob_storage_context)); - } - if (pending_file_system_access_context) { - file_system_access_context->Bind( - std::move(pending_file_system_access_context)); - } - }, - &blob_storage_context_, &file_system_access_context_, - "a_client_receiver_, std::move(blob_storage_context), - std::move(file_system_access_context), - std::move(quota_client_receiver))); + IDBTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&IndexedDBContextImpl::BindPipesOnIDBSequence, + weak_factory_.GetWeakPtr(), + std::move(quota_client_receiver), + std::move(blob_storage_context), + std::move(file_system_access_context))); +} + +void IndexedDBContextImpl::BindPipesOnIDBSequence( + mojo::PendingReceiver + pending_quota_client_receiver, + mojo::PendingRemote + pending_blob_storage_context, + mojo::PendingRemote + pending_file_system_access_context) { + DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); + if (pending_quota_client_receiver) { + quota_client_receiver_.Bind(std::move(pending_quota_client_receiver)); + } + if (pending_blob_storage_context) { + blob_storage_context_.Bind(std::move(pending_blob_storage_context)); + } + if (pending_file_system_access_context) { + file_system_access_context_.Bind( + std::move(pending_file_system_access_context)); + } } void IndexedDBContextImpl::Bind( @@ -466,7 +461,7 @@ void IndexedDBContextImpl::GetAllStorageKeysDetails( } void IndexedDBContextImpl::SetForceKeepSessionState() { - idb_task_runner_->PostTask( + IDBTaskRunner()->PostTask( FROM_HERE, base::BindOnce( [](IndexedDBContextImpl* context) { @@ -877,7 +872,7 @@ void IndexedDBContextImpl::BindIndexedDBWithBucket( } void IndexedDBContextImpl::ShutdownOnIDBSequence() { - DCHECK(idb_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence()); if (force_keep_session_state_) return; @@ -909,7 +904,7 @@ void IndexedDBContextImpl::Shutdown() { if (is_incognito()) return; - idb_task_runner_->PostTask( + IDBTaskRunner()->PostTask( FROM_HERE, base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence, base::WrapRefCounted(this))); } diff --git a/src/content/browser/indexed_db/indexed_db_context_impl.h b/src/content/browser/indexed_db/indexed_db_context_impl.h index 4b874b244778d..ad2f984e130ec --- a/src/content/browser/indexed_db/indexed_db_context_impl.h +++ b/src/content/browser/indexed_db/indexed_db_context_impl.h @@ -224,6 +224,14 @@ class CONTENT_EXPORT IndexedDBContextImpl ~IndexedDBContextImpl() override; + void BindPipesOnIDBSequence( + mojo::PendingReceiver + pending_quota_client_receiver, + mojo::PendingRemote + pending_blob_storage_context, + mojo::PendingRemote + pending_file_system_access_context); + // Binds receiver on bucket retrieval to ensure that a bucket always exists // for a storage key. void BindIndexedDBWithBucket( @@ -282,6 +290,8 @@ class CONTENT_EXPORT IndexedDBContextImpl mojo::Receiver quota_client_receiver_; const std::unique_ptr filesystem_proxy_; + // weak_factory_->GetWeakPtr() may be used on any thread, but the resulting + // pointer must only be checked/used on idb_task_runner_. base::WeakPtrFactory weak_factory_{this}; }; diff --git a/src/content/browser/loader/object_navigation_fallback_body_loader.cc b/src/content/browser/loader/object_navigation_fallback_body_loader.cc index 03f5b39b3e3ed..84bcfc60af6a3 --- a/src/content/browser/loader/object_navigation_fallback_body_loader.cc +++ b/src/content/browser/loader/object_navigation_fallback_body_loader.cc @@ -157,10 +157,6 @@ blink::mojom::ResourceTimingInfoPtr GenerateResourceTiming( timing_info->allow_redirect_details = timing_info->allow_timing_details; timing_info->last_redirect_end_time = commit_params.redirect_response.back()->load_timing.receive_headers_end; - - if (!timing_info->allow_redirect_details) { - timing_info->start_time = response_head.load_timing.request_start; - } } else { timing_info->allow_redirect_details = false; timing_info->last_redirect_end_time = base::TimeTicks(); diff --git a/src/content/browser/media/capture/frame_sink_video_capture_device.cc b/src/content/browser/media/capture/frame_sink_video_capture_device.cc index 923ed80023641..720c3317e69d4 --- a/src/content/browser/media/capture/frame_sink_video_capture_device.cc +++ b/src/content/browser/media/capture/frame_sink_video_capture_device.cc @@ -290,17 +290,17 @@ void FrameSinkVideoCaptureDevice::OnStopped() { } void FrameSinkVideoCaptureDevice::OnLog(const std::string& message) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + GetIOThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&FrameSinkVideoCaptureDevice::OnLog, + weak_factory_.GetWeakPtr(), message)); + return; + } + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (receiver_) { - if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { - receiver_->OnLog(message); - } else { - GetIOThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&media::VideoFrameReceiver::OnLog, - base::Unretained(receiver_.get()), message)); - } + receiver_->OnLog(message); } } diff --git a/src/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc b/src/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc index 9077a176fff2a..4065d005ce7a6 --- a/src/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc +++ b/src/content/browser/renderer_host/back_forward_cache_can_store_document_result.cc @@ -181,6 +181,8 @@ ProtoEnum::BackForwardCacheNotRestoredReason NotRestoredReasonToTraceEnum( return ProtoEnum::CACHE_CONTROL_NO_STORE_HTTP_ONLY_COOKIE_MODIFIED; case Reason::kNoResponseHead: return ProtoEnum::NO_RESPONSE_HEAD; + case Reason::kErrorDocument: + return ProtoEnum::ERROR_DOCUMENT; case Reason::kActivationNavigationsDisallowedForBug1234857: return ProtoEnum::ACTIVATION_NAVIGATION_DISALLOWED_FOR_BUG_1234857; case Reason::kBlocklistedFeatures: @@ -392,6 +394,8 @@ std::string BackForwardCacheCanStoreDocumentResult::NotRestoredReasonToString( case Reason::kNoResponseHead: return "main RenderFrameHost doesn't have response headers set, probably " "due not having successfully committed a navigation."; + case Reason::kErrorDocument: + return "Error documents cannot be stored in bfcache"; case Reason::kActivationNavigationsDisallowedForBug1234857: return "Activation navigations are disallowed to avoid bypassing " "PasswordProtectionService as a workaround for " diff --git a/src/content/browser/renderer_host/back_forward_cache_impl.cc b/src/content/browser/renderer_host/back_forward_cache_impl.cc index 12bb65f5a1a68..5d63c05150388 --- a/src/content/browser/renderer_host/back_forward_cache_impl.cc +++ b/src/content/browser/renderer_host/back_forward_cache_impl.cc @@ -763,6 +763,13 @@ void BackForwardCacheImpl::PopulateReasonsForMainDocument( if (rfh->last_http_status_code() != net::HTTP_OK) result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK); + // Interstitials and other internal error pages should set an error status + // code but there's no guarantee, e.g. https://crbug/1274308, + // https://crbug/1287996. This catches those cases. It might also make the + // kHTTPStatusNotOK check redundant. + if (rfh->IsErrorDocument()) + result.No(BackForwardCacheMetrics::NotRestoredReason::kErrorDocument); + // Only store documents that were fetched via HTTP GET method. if (rfh->last_http_method() != net::HttpRequestHeaders::kGetMethod) result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPMethodNotGET); diff --git a/src/content/browser/renderer_host/back_forward_cache_metrics.h b/src/content/browser/renderer_host/back_forward_cache_metrics.h index 763ebd117f58b..6dae020380749 --- a/src/content/browser/renderer_host/back_forward_cache_metrics.h +++ b/src/content/browser/renderer_host/back_forward_cache_metrics.h @@ -111,7 +111,8 @@ class BackForwardCacheMetrics kCacheControlNoStoreHTTPOnlyCookieModified = 55, kNoResponseHead = 56, kActivationNavigationsDisallowedForBug1234857 = 57, - kMaxValue = kActivationNavigationsDisallowedForBug1234857, + kErrorDocument = 58, + kMaxValue = kErrorDocument, }; using NotRestoredReasons = diff --git a/src/content/browser/renderer_host/frame_tree.cc b/src/content/browser/renderer_host/frame_tree.cc index 33b4aa5a10a57..e750a4f2d9e10 --- a/src/content/browser/renderer_host/frame_tree.cc +++ b/src/content/browser/renderer_host/frame_tree.cc @@ -788,7 +788,7 @@ void FrameTree::RegisterExistingOriginToPreventOptInIsolation( void FrameTree::Init(SiteInstance* main_frame_site_instance, bool renderer_initiated_creation, const std::string& main_frame_name, - RenderFrameHostImpl* opener, + RenderFrameHostImpl* opener_for_origin, const blink::FramePolicy& frame_policy) { // blink::FrameTree::SetName always keeps |unique_name| empty in case of a // main frame - let's do the same thing here. @@ -800,16 +800,16 @@ void FrameTree::Init(SiteInstance* main_frame_site_instance, // The initial empty document should inherit the origin of its opener (the // origin may change after the first commit), except when they are in - // different browsing context groups (`renderer_initiated_creation` is false), - // where it should use a new opaque origin. + // different browsing context groups (`renderer_initiated_creation` will be + // false), where it should use a new opaque origin. // See also https://crbug.com/932067. // // Note that the origin of the new frame might depend on sandbox flags. // Checking sandbox flags of the new frame should be safe at this point, // because the flags should be already inherited when creating the root node. - DCHECK(!renderer_initiated_creation || opener); + DCHECK(!renderer_initiated_creation || opener_for_origin); root_->current_frame_host()->SetOriginDependentStateOfNewFrame( - renderer_initiated_creation ? opener->GetLastCommittedOrigin() + renderer_initiated_creation ? opener_for_origin->GetLastCommittedOrigin() : url::Origin()); if (blink::features::IsInitialNavigationEntryEnabled()) diff --git a/src/content/browser/renderer_host/frame_tree.h b/src/content/browser/renderer_host/frame_tree.h index ce0b5df43ff71..db82e518da8d3 --- a/src/content/browser/renderer_host/frame_tree.h +++ b/src/content/browser/renderer_host/frame_tree.h @@ -204,7 +204,7 @@ class CONTENT_EXPORT FrameTree { // Initializes the main frame for this FrameTree. That is it creates the // initial RenderFrameHost in the root node's RenderFrameHostManager, and also // creates an initial NavigationEntry (if the InitialNavigationEntry feature - // is enabled) that potentially inherits `opener`'s origin in its + // is enabled) that potentially inherits `opener_for_origin`'s origin in its // NavigationController. This method will call back into the delegates so it // should only be called once they have completed their initialization. Pass // in frame_policy so that it can be set in the root node's replication_state. @@ -213,7 +213,7 @@ class CONTENT_EXPORT FrameTree { void Init(SiteInstance* main_frame_site_instance, bool renderer_initiated_creation, const std::string& main_frame_name, - RenderFrameHostImpl* opener, + RenderFrameHostImpl* opener_for_origin, const blink::FramePolicy& frame_policy); Type type() const { return type_; } diff --git a/src/content/browser/renderer_host/frame_tree_node.cc b/src/content/browser/renderer_host/frame_tree_node.cc index 7dea0e9ca2d85..f757c2ac3d382 --- a/src/content/browser/renderer_host/frame_tree_node.cc +++ b/src/content/browser/renderer_host/frame_tree_node.cc @@ -50,7 +50,7 @@ base::LazyInstance::DestructorAtExit } // namespace // This observer watches the opener of its owner FrameTreeNode and clears the -// owner's opener if the opener is destroyed. +// owner's opener if the opener is destroyed or swaps BrowsingInstance. class FrameTreeNode::OpenerDestroyedObserver : public FrameTreeNode::Observer { public: OpenerDestroyedObserver(FrameTreeNode* owner, bool observing_original_opener) @@ -61,6 +61,15 @@ class FrameTreeNode::OpenerDestroyedObserver : public FrameTreeNode::Observer { // FrameTreeNode::Observer void OnFrameTreeNodeDestroyed(FrameTreeNode* node) override { + NullifyOpener(node); + } + + // FrameTreeNode::Observer + void OnFrameTreeNodeDisownedOpenee(FrameTreeNode* node) override { + NullifyOpener(node); + } + + void NullifyOpener(FrameTreeNode* node) { if (observing_original_opener_) { // The "original owner" is special. It's used for attribution, and clients // walk down the original owner chain. Therefore, if a link in the chain @@ -858,4 +867,13 @@ bool FrameTreeNode::IsErrorPageIsolationEnabled() const { IsFencedFrameRoot()); } +void FrameTreeNode::ClearOpenerReferences() { + // Simulate the FrameTreeNode being dead to opener observers. They will + // nullify their opener. + // Note: observers remove themselves from observers_, no need to take care of + // that manually. + for (auto& observer : observers_) + observer.OnFrameTreeNodeDisownedOpenee(this); +} + } // namespace content diff --git a/src/content/browser/renderer_host/frame_tree_node.h b/src/content/browser/renderer_host/frame_tree_node.h index f4aee2f93fb8f..0fb637a58ae8e --- a/src/content/browser/renderer_host/frame_tree_node.h +++ b/src/content/browser/renderer_host/frame_tree_node.h @@ -63,6 +63,10 @@ class CONTENT_EXPORT FrameTreeNode { // Invoked when a FrameTreeNode becomes focused. virtual void OnFrameTreeNodeFocused(FrameTreeNode* node) {} + // Invoked when a FrameTreeNode moves to a different BrowsingInstance and + // the popups it opened should be disowned. + virtual void OnFrameTreeNodeDisownedOpenee(FrameTreeNode* node) {} + virtual ~Observer() = default; }; @@ -505,6 +509,10 @@ class CONTENT_EXPORT FrameTreeNode { // Returns true if error page isolation is enabled. bool IsErrorPageIsolationEnabled() const; + // Clears the opener property of popups referencing this FrameTreeNode as + // their opener. + void ClearOpenerReferences(); + private: FRIEND_TEST_ALL_PREFIXES(SitePerProcessPermissionsPolicyBrowserTest, ContainerPolicyDynamic); diff --git a/src/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/src/content/browser/renderer_host/media/media_stream_dispatcher_host.cc index e8779a7afebed..e7f818fa91b24 --- a/src/content/browser/renderer_host/media/media_stream_dispatcher_host.cc +++ b/src/content/browser/renderer_host/media/media_stream_dispatcher_host.cc @@ -18,6 +18,7 @@ #include "content/browser/renderer_host/media/video_capture_manager.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -69,26 +70,40 @@ StartObservingWebContents(int render_process_id, } #if !BUILDFLAG(IS_ANDROID) +// Helper for getting the top-level WebContents associated with a given ID. +// Returns nullptr if one does not exist (e.g. has gone away). +WebContents* GetMainFrameWebContents(const GlobalRoutingID& global_routing_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (global_routing_id == GlobalRoutingID()) { + return nullptr; + } + + RenderFrameHost* const rfh = RenderFrameHost::FromID( + global_routing_id.child_id, global_routing_id.route_id); + return rfh ? WebContents::FromRenderFrameHost(rfh->GetMainFrame()) : nullptr; +} + // Checks whether a track living in the WebContents indicated by // (render_process_id, render_frame_id) may be cropped to the crop-target // indicated by |crop_id|. -bool IsCropTargetValid(int render_process_id, - int render_frame_id, - const base::Token& crop_id) { - RenderFrameHost* const rfh = - RenderFrameHost::FromID(render_process_id, render_frame_id); - if (!rfh) { +bool MayCrop(const GlobalRoutingID& capturing_id, + const GlobalRoutingID& captured_id, + const base::Token& crop_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + WebContents* const capturing_wc = GetMainFrameWebContents(capturing_id); + if (!capturing_wc) { return false; } - WebContents* const web_contents = - WebContents::FromRenderFrameHost(rfh->GetMainFrame()); - if (!web_contents) { + WebContents* const captured_wc = GetMainFrameWebContents(captured_id); + if (capturing_wc != captured_wc) { // Null or not-same-tab. return false; } CropIdWebContentsHelper* const helper = - CropIdWebContentsHelper::FromWebContents(web_contents); + CropIdWebContentsHelper::FromWebContents(captured_wc); if (!helper) { // No crop-IDs were ever produced on this WebContents. // Any non-zero crop-ID should be rejected on account of being @@ -512,14 +527,19 @@ void MediaStreamDispatcherHost::Crop(const base::UnguessableToken& device_id, CropCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); + const GlobalRoutingID captured_id = + media_stream_manager_->video_capture_manager()->GetGlobalRoutingID( + device_id); + // Hop to the UI thread to verify that cropping to |crop_id| is permitted // from this particular context. Namely, cropping is currently only allowed // for self-capture, so the crop_id has to be associated with the top-level // WebContents belonging to this very tab. GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( FROM_HERE, - base::BindOnce(&IsCropTargetValid, render_process_id_, render_frame_id_, - crop_id), + base::BindOnce(&MayCrop, + GlobalRoutingID(render_process_id_, render_frame_id_), + captured_id, crop_id), base::BindOnce(&MediaStreamDispatcherHost::OnCropValidationComplete, weak_factory_.GetWeakPtr(), device_id, crop_id, std::move(callback))); diff --git a/src/content/browser/renderer_host/media/video_capture_manager.cc b/src/content/browser/renderer_host/media/video_capture_manager.cc index d2d601cc7a91b..9661bb83ad9d4 --- a/src/content/browser/renderer_host/media/video_capture_manager.cc +++ b/src/content/browser/renderer_host/media/video_capture_manager.cc @@ -594,6 +594,29 @@ VideoCaptureManager::GetDeviceFormatInUse( return device_in_use ? device_in_use->GetVideoCaptureFormat() : absl::nullopt; } +GlobalRoutingID VideoCaptureManager::GetGlobalRoutingID( + const base::UnguessableToken& session_id) const { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + VideoCaptureController* const controller = + LookupControllerBySessionId(session_id); + if (!controller || !controller->IsDeviceAlive() || + !blink::IsVideoDesktopCaptureMediaType(controller->stream_type())) { + return GlobalRoutingID(); + } + + const DesktopMediaID desktop_media_id = + DesktopMediaID::Parse(controller->device_id()); + + if (desktop_media_id.type != DesktopMediaID::Type::TYPE_WEB_CONTENTS || + desktop_media_id.web_contents_id.is_null()) { + return GlobalRoutingID(); + } + + return GlobalRoutingID(desktop_media_id.web_contents_id.render_process_id, + desktop_media_id.web_contents_id.main_render_frame_id); +} + void VideoCaptureManager::SetDesktopCaptureWindowId( const media::VideoCaptureSessionId& session_id, gfx::NativeViewId window_id) { @@ -796,7 +819,7 @@ void VideoCaptureManager::DestroyControllerIfNoClients( } VideoCaptureController* VideoCaptureManager::LookupControllerBySessionId( - const base::UnguessableToken& session_id) { + const base::UnguessableToken& session_id) const { DCHECK_CURRENTLY_ON(BrowserThread::IO); SessionMap::const_iterator session_it = sessions_.find(session_id); if (session_it == sessions_.end()) diff --git a/src/content/browser/renderer_host/media/video_capture_manager.h b/src/content/browser/renderer_host/media/video_capture_manager.h index 4647f39e8ff2f..73e884b3ce9f4 --- a/src/content/browser/renderer_host/media/video_capture_manager.h +++ b/src/content/browser/renderer_host/media/video_capture_manager.h @@ -24,6 +24,7 @@ #include "content/browser/renderer_host/media/video_capture_device_launch_observer.h" #include "content/browser/renderer_host/media/video_capture_provider.h" #include "content/common/content_export.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/screenlock_observer.h" #include "media/base/video_facing.h" #include "media/capture/video/video_capture_device.h" @@ -169,6 +170,12 @@ class CONTENT_EXPORT VideoCaptureManager blink::mojom::MediaStreamType stream_type, const std::string& device_id); + // If there is a capture session associated with |session_id|, and the + // captured entity a tab, return the GlobalRoutingID of the captured tab. + // Otherwise, returns an empty GlobalRoutingID. + GlobalRoutingID GetGlobalRoutingID( + const base::UnguessableToken& session_id) const; + // Sets the platform-dependent window ID for the desktop capture notification // UI for the given session. void SetDesktopCaptureWindowId(const media::VideoCaptureSessionId& session_id, @@ -242,7 +249,7 @@ class CONTENT_EXPORT VideoCaptureManager // |device_id| and |type| (if it is already opened), by its |controller| or by // its |serial_id|. In all cases, if not found, nullptr is returned. VideoCaptureController* LookupControllerBySessionId( - const base::UnguessableToken& session_id); + const base::UnguessableToken& session_id) const; VideoCaptureController* LookupControllerByMediaTypeAndDeviceId( blink::mojom::MediaStreamType type, const std::string& device_id) const; diff --git a/src/content/browser/renderer_host/render_frame_host_impl.cc b/src/content/browser/renderer_host/render_frame_host_impl.cc index 6461a3b9ae72c..fc433bec21040 --- a/src/content/browser/renderer_host/render_frame_host_impl.cc +++ b/src/content/browser/renderer_host/render_frame_host_impl.cc @@ -1486,7 +1486,12 @@ RenderFrameHostImpl::~RenderFrameHostImpl() { // Release the WebUI instances before all else as the WebUI may accesses the // RenderFrameHost during cleanup. + base::WeakPtr self = GetWeakPtr(); ClearWebUI(); + // `ClearWebUI()` may indirectly call content's embedders and delete this. + // There are no known occurrences of it, so we assume this never happen and + // crash immediately if it does, because there are no easy ways to recover. + CHECK(self); SetLastCommittedSiteInfo(GURL()); @@ -2801,7 +2806,7 @@ void RenderFrameHostImpl::RenderProcessExited( SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted); DCHECK(children_.empty()); - PendingDeletionCheckCompleted(); + PendingDeletionCheckCompleted(); // Can delete |this|. // |this| is deleted. Don't add any more code at this point in the function. return; } @@ -3793,7 +3798,8 @@ void RenderFrameHostImpl::RemoveChild(FrameTreeNode* child) { // and `~RenderFrameProxyHost()` sends a Mojo `DetachAndDispose()` IPC for // child frame proxies. node_to_delete.reset(); - PendingDeletionCheckCompleted(); + PendingDeletionCheckCompleted(); // Can delete |this|. + // |this| is potentially deleted. Do not add code after this. return; } } @@ -3867,6 +3873,7 @@ void RenderFrameHostImpl::Detach() { if (lifecycle_state() != LifecycleStateImpl::kReadyToBeDeleted) SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted); PendingDeletionCheckCompleted(); // Can delete |this|. + // |this| is potentially deleted. Do not add code after this. return; } @@ -3883,6 +3890,7 @@ void RenderFrameHostImpl::Detach() { // Some children with no unload handler may be eligible for immediate // deletion. Cut the dead branches now. This is a performance optimization. PendingDeletionCheckCompletedOnSubtree(); // Can delete |this|. + // |this| is potentially deleted. Do not add code after this. } void RenderFrameHostImpl::DidFailLoadWithError(const GURL& url, @@ -4370,6 +4378,7 @@ void RenderFrameHostImpl::DetachFromProxy() { // Some children with no unload handler may be eligible for immediate // deletion. Cut the dead branches now. This is a performance optimization. PendingDeletionCheckCompletedOnSubtree(); // May delete |this|. + // |this| is potentially deleted. Do not add code after this. } void RenderFrameHostImpl::ProcessBeforeUnloadCompleted( @@ -4573,6 +4582,7 @@ void RenderFrameHostImpl::OnUnloadACK() { DCHECK_EQ(LifecycleStateImpl::kRunningUnloadHandlers, lifecycle_state()); SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted); PendingDeletionCheckCompleted(); // Can delete |this|. + // |this| is potentially deleted. Do not add code after this. } void RenderFrameHostImpl::OnUnloaded() { @@ -4583,7 +4593,14 @@ void RenderFrameHostImpl::OnUnloaded() { if (unload_event_monitor_timeout_) unload_event_monitor_timeout_->Stop(); + base::WeakPtr self = GetWeakPtr(); ClearWebUI(); + // See https://crbug.com/1308391. Calling `ClearWebUI()` indirectly call + // content's embedders via a chain of destructors. Some might destroy the + // whole WebContents. + if (!self) { + return; + } bool deleted = frame_tree_node_->render_manager()->DeleteFromPendingList(this); @@ -6635,18 +6652,23 @@ void RenderFrameHostImpl::CreateNewWindow( effective_transient_activation_state, params->opener_suppressed, &no_javascript_access); - bool was_consumed = false; - if (can_create_window) { - // Consume activation even w/o User Activation v2, to sync other renderers - // with calling renderer. - was_consumed = frame_tree_node_->UpdateUserActivationState( - blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, - blink::mojom::UserActivationNotificationType::kNone); - } else { - std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr); + // If this frame isn't allowed to create a window, return early (before we + // consume transient user activation). + if (!can_create_window) { + std::move(callback).Run(mojom::CreateNewWindowStatus::kBlocked, nullptr); return; } + // Otherwise, consume user activation before we proceed. In particular, it is + // important to do this before we return from the |opener_suppressed| case + // below. + // NB: This call will consume activations in the browser and the remote frame + // proxies for this frame. The initiating renderer will consume its view of + // the activations after we return. + bool was_consumed = frame_tree_node_->UpdateUserActivationState( + blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, + blink::mojom::UserActivationNotificationType::kNone); + // For Android WebView, we support a pop-up like behavior for window.open() // even if the embedding app doesn't support multiple windows. In this case, // window.open() will return "window" and navigate it to whatever URL was @@ -7671,16 +7693,19 @@ void RenderFrameHostImpl::StartPendingDeletionOnSubtree() { void RenderFrameHostImpl::PendingDeletionCheckCompleted() { if (lifecycle_state() == LifecycleStateImpl::kReadyToBeDeleted && children_.empty()) { - if (is_waiting_for_unload_ack_) - OnUnloaded(); - else + if (is_waiting_for_unload_ack_) { + OnUnloaded(); // Delete |this|. + // Do not add code after this. + } else { parent_->RemoveChild(frame_tree_node_); + } } } void RenderFrameHostImpl::PendingDeletionCheckCompletedOnSubtree() { if (children_.empty()) { PendingDeletionCheckCompleted(); + // |this| is potentially deleted. Do not add code after this. return; } @@ -8601,7 +8626,12 @@ bool RenderFrameHostImpl::CreateWebUI(const GURL& dest_url, if (entry_bindings != FrameNavigationEntry::kInvalidBindings && web_ui_->GetBindings() != entry_bindings) { RecordAction(base::UserMetricsAction("ProcessSwapBindingsMismatch_RVHM")); + base::WeakPtr self = GetWeakPtr(); ClearWebUI(); + // `ClearWebUI()` may indirectly call content's embedders and delete this. + // There are no known occurrences of it, so we assume this never happen and + // crash immediately if it does, because there are no easy ways to recover. + CHECK(self); return false; } @@ -8626,7 +8656,8 @@ bool RenderFrameHostImpl::CreateWebUI(const GURL& dest_url, void RenderFrameHostImpl::ClearWebUI() { web_ui_type_ = WebUI::kNoWebUI; - web_ui_.reset(); + web_ui_.reset(); // This might delete `this`. + // DO NOT ADD CODE after this. } const mojo::Remote& diff --git a/src/content/browser/renderer_host/render_frame_host_impl.h b/src/content/browser/renderer_host/render_frame_host_impl.h index 26a96fb437747..330fdd1ed2c63 --- a/src/content/browser/renderer_host/render_frame_host_impl.h +++ b/src/content/browser/renderer_host/render_frame_host_impl.h @@ -1244,6 +1244,8 @@ class CONTENT_EXPORT RenderFrameHostImpl bool CreateWebUI(const GURL& dest_url, int entry_bindings); // Destroys WebUI instance and resets related data. + // This indirectly calls content's embedders and may have arbitrary side + // effect, like deleting `this`. void ClearWebUI(); // Returns the Mojo ImageDownloader service. diff --git a/src/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/src/content/browser/renderer_host/render_frame_host_impl_browsertest.cc index b91c1bff4ecdf..b8920f90237c4 --- a/src/content/browser/renderer_host/render_frame_host_impl_browsertest.cc +++ b/src/content/browser/renderer_host/render_frame_host_impl_browsertest.cc @@ -6441,6 +6441,80 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, ErrorDocuments) { EXPECT_TRUE(child_b->IsErrorDocument()); } +// Tests that a popup that is opened by a subframe inherits the subframe's +// origin, instead of the main frame's origin. +// Regression test for https://crbug.com/1311820 and https://crbug.com/1291764. +IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, + PopupOpenedBySubframeHasCorrectOrigin) { + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b)")); + // Navigate to a page with a cross-site iframe. + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); + FrameTreeNode* child = root->child_at(0); + + // Verify that the main frame & subframe origin differs. + url::Origin a_origin = url::Origin::Create(main_url); + url::Origin b_origin = url::Origin::Create(child->current_url()); + EXPECT_EQ(a_origin, root->current_frame_host()->GetLastCommittedOrigin()); + EXPECT_EQ(b_origin, child->current_frame_host()->GetLastCommittedOrigin()); + EXPECT_NE(a_origin, b_origin); + + { + // From the subframe, open a popup that stays on the initial empty + // document. + WebContentsAddedObserver popup_observer; + ASSERT_TRUE(ExecJs(child, "var w = window.open('/nocontent');")); + WebContentsImpl* popup = + static_cast(popup_observer.GetWebContents()); + FrameTreeNode* popup_frame = popup->GetMainFrame()->frame_tree_node(); + + // The popup should inherit the subframe's origin. Before the fix for + // https://crbug.com/1311820, the popup used to inherit the main frame's + // origin instead. + EXPECT_EQ(b_origin, + popup_frame->current_frame_host()->GetLastCommittedOrigin()); + EXPECT_EQ(b_origin.Serialize(), EvalJs(popup_frame, "self.origin")); + + // Try calling document.open() on the popup from itself. + // This used to cause a renderer kill as the browser used to notice the + // current origin & process lock mismatched when the document.open() + // notification IPC arrives. + EXPECT_EQ(GURL("about:blank"), EvalJs(popup_frame, "location.href")); + EXPECT_TRUE(ExecJs(popup_frame, "document.open()")); + EXPECT_EQ(GURL("about:blank"), EvalJs(popup_frame, "location.href")); + + // Try updating the URL of the popup to the opener subframe's URL by + // calling document.open() on the popup from the opener subframe. + // This used to cause a renderer kill as the browser used to expect that + // the popup frame can only update to URLs under `a_origin`, while the + // new URL is under `b_origin`. See also https://crbug.com/1291764. + EXPECT_TRUE(ExecJs(child, "w.document.open()")); + EXPECT_EQ(child->current_url().spec(), + EvalJs(popup_frame, "location.href")); + } + + { + // From the subframe, open a popup that stays on the initial empty + // document, and specify 'noopener' to sever the opener relationship. + WebContentsAddedObserver popup_observer; + ASSERT_TRUE( + ExecJs(child, "var w = window.open('/nocontent', '', 'noopener');")); + WebContentsImpl* popup = + static_cast(popup_observer.GetWebContents()); + FrameTreeNode* popup_frame = popup->GetMainFrame()->frame_tree_node(); + EXPECT_EQ(nullptr, EvalJs(popup_frame, "window.opener")); + + // The popup should use a new opaque origin, instead of the subframe's + // origin. + EXPECT_NE(b_origin, + popup_frame->current_frame_host()->GetLastCommittedOrigin()); + EXPECT_TRUE( + popup_frame->current_frame_host()->GetLastCommittedOrigin().opaque()); + EXPECT_EQ("null", EvalJs(popup_frame, "self.origin")); + } +} + class RenderFrameHostImplAvoidUnnecessaryBeforeUnloadBrowserTest : public RenderFrameHostImplBeforeUnloadBrowserTest { public: diff --git a/src/content/browser/renderer_host/render_frame_host_manager.cc b/src/content/browser/renderer_host/render_frame_host_manager.cc index be63f30c49416..4eff902710ed4 --- a/src/content/browser/renderer_host/render_frame_host_manager.cc +++ b/src/content/browser/renderer_host/render_frame_host_manager.cc @@ -3450,9 +3450,19 @@ void RenderFrameHostManager::CommitPending( // If this is a top-level frame, and COOP triggered a BrowsingInstance swap, // make sure all relationships with the previous BrowsingInstance are severed - // by removing the opener and proxies with unrelated SiteInstances. + // by removing the opener, the openee's opener, and the proxies with unrelated + // SiteInstances. if (clear_proxies_on_commit) { DCHECK(frame_tree_node_->IsMainFrame()); + + // If this frame has opened popups, we need to clear the opened popup's + // opener. This is done here on the browser side. A similar mechanism occurs + // in the renderer process when the RenderView of this frame is destroyed, + // via blink::OpenedFrameTracker. + frame_tree_node_->ClearOpenerReferences(); + + // We've just cleared other frames' "opener" referencing this frame, we now + // clear this frame's "opener". if (frame_tree_node_->opener() && !render_frame_host_->GetSiteInstance()->IsRelatedSiteInstance( frame_tree_node_->opener() @@ -3464,6 +3474,8 @@ void RenderFrameHostManager::CommitPending( // after it is not necessary in this particuliar case. } + // Now that opener references are gone in both direction, we can clear the + // underlying proxies that were used for that purpose. std::vector removed_proxies; for (auto& it : browsing_context_state_->proxy_hosts()) { const auto& proxy = it.second; diff --git a/src/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/src/content/browser/renderer_host/render_widget_host_view_android_unittest.cc index 7605ea7732c81..7fc33c81cced3 --- a/src/content/browser/renderer_host/render_widget_host_view_android_unittest.cc +++ b/src/content/browser/renderer_host/render_widget_host_view_android_unittest.cc @@ -276,9 +276,9 @@ TEST_F(RenderWidgetHostViewAndroidTest, InsetVisualViewport) { } TEST_F(RenderWidgetHostViewAndroidTest, HideWindowRemoveViewAddViewShowWindow) { - std::unique_ptr window( - ui::WindowAndroid::CreateForTesting()); - window->AddChild(parent_view()); + std::unique_ptr window = + ui::WindowAndroid::CreateForTesting(); + window->get()->AddChild(parent_view()); EXPECT_TRUE(render_widget_host_view_android()->IsShowing()); // The layer should be visible once attached to a window. EXPECT_FALSE(render_widget_host_view_android() @@ -287,7 +287,7 @@ TEST_F(RenderWidgetHostViewAndroidTest, HideWindowRemoveViewAddViewShowWindow) { ->hide_layer_and_subtree()); // Hiding the window should and removing the view should hide the layer. - window->OnVisibilityChanged(nullptr, nullptr, false); + window->get()->OnVisibilityChanged(nullptr, nullptr, false); parent_view()->RemoveFromParent(); EXPECT_TRUE(render_widget_host_view_android()->IsShowing()); EXPECT_TRUE(render_widget_host_view_android() @@ -297,8 +297,8 @@ TEST_F(RenderWidgetHostViewAndroidTest, HideWindowRemoveViewAddViewShowWindow) { // Adding the view back to a window and notifying the window is visible should // make the layer visible again. - window->AddChild(parent_view()); - window->OnVisibilityChanged(nullptr, nullptr, true); + window->get()->AddChild(parent_view()); + window->get()->OnVisibilityChanged(nullptr, nullptr, true); EXPECT_TRUE(render_widget_host_view_android()->IsShowing()); EXPECT_FALSE(render_widget_host_view_android() ->GetNativeView() diff --git a/src/content/browser/site_per_process_browsertest.cc b/src/content/browser/site_per_process_browsertest.cc index 9df4e4cfd00c7..3609e9ffe9d9a --- a/src/content/browser/site_per_process_browsertest.cc +++ b/src/content/browser/site_per_process_browsertest.cc @@ -7700,8 +7700,8 @@ IN_PROC_BROWSER_TEST_P( #else #define MAYBE_CrossProcessInertSubframe CrossProcessInertSubframe #endif -// Tests that when a frame contains a modal element, out-of-process -// iframe children cannot take focus, because they are inert. +// Tests that when an out-of-process iframe becomes inert due to a modal +// element, the contents of the iframe can still take focus. IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, MAYBE_CrossProcessInertSubframe) { // This uses a(b,b) instead of a(b) to preserve the b.com process even when @@ -7742,11 +7742,11 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, std::string focused_element; - // Attempt to change focus in the inert subframe. This should fail. + // Attempt to change focus in the inert subframe. This should work. // The setTimeout ensures that the inert bit can propagate before the // test JS code runs. EXPECT_EQ( - "", + "text2", EvalJs(iframe_node, "window.setTimeout(() => {text2.focus();" "domAutomationController.send(document.activeElement.id);}, 0)", @@ -7768,15 +7768,15 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, "document.body.innerHTML = ' ';" "text1.focus();")); - // Verify that inertness was preserved across the navigation. - EXPECT_EQ("", + // Verify we can still set focus after the navigation. + EXPECT_EQ("text2", EvalJs(iframe_node, "text2.focus();" "domAutomationController.send(document.activeElement.id);", EXECUTE_SCRIPT_USE_MANUAL_REPLY)); // Navigate the subframe back into its parent process to verify that the - // new local frame remains inert. + // new local frame remains non-inert. GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title1.html")); EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, same_site_url)); @@ -7786,14 +7786,103 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, "document.body.innerHTML = ' ';" "text1.focus();")); - // Verify that inertness was preserved across the navigation. - EXPECT_EQ("", + // Verify we can still set focus after the navigation. + EXPECT_EQ("text2", EvalJs(iframe_node, "text2.focus();" "domAutomationController.send(document.activeElement.id);", EXECUTE_SCRIPT_USE_MANUAL_REPLY)); } +// Tests that IsInert frame flag is correctly updated and propagated. +IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, + CrossProcessIsInertPropagation) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kEnableBlinkFeatures, "InertAttribute"); + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b(c))")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + FrameTreeNode* frame_a = + static_cast(shell()->web_contents()) + ->GetPrimaryFrameTree() + .root(); + ASSERT_EQ(1U, frame_a->child_count()); + FrameTreeNode* frame_b = frame_a->child_at(0); + ASSERT_EQ(1U, frame_b->child_count()); + FrameTreeNode* frame_c = frame_b->child_at(0); + RenderFrameProxyHost* proxy_b = frame_b->render_manager()->GetProxyToParent(); + RenderFrameProxyHost* proxy_c = frame_c->render_manager()->GetProxyToParent(); + + auto waitForInertPropagated = [&]() { + // Force layout. This recomputes the element styles so that the +
+ +
+
+

+
+ + + diff --git a/src/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/detached-iframe.window.js b/src/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/detached-iframe.window.js new file mode 100644 index 0000000000000..48bbcc3dd6c97 --- /dev/null +++ b/src/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/detached-iframe.window.js @@ -0,0 +1,47 @@ +// META: title=MessageChannel in a detached iframe test +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// Pull in the with_iframe helper function from the service worker tests + + +const IframeAction = { + REMOVE_BEFORE_CREATION: 'remove-before-creation', + REMOVE_AFTER_CREATION: 'remove-after-creation', +}; + +async function detached_frame_test(t, action) { + const iframe = await with_iframe('about:blank'); + const iframe_MessageChannel = iframe.contentWindow.MessageChannel; + + if (action === IframeAction.REMOVE_BEFORE_CREATION) { + iframe.remove(); + } + + (() => { + const mc = new iframe_MessageChannel(); + mc.port1.postMessage("boo"); + mc.port2.onmessage = t.unreached_func("message event received"); + mc.port2.onmessageerror = t.unreached_func("message event received"); + })(); + + if (action === IframeAction.REMOVE_AFTER_CREATION) { + iframe.remove(); + } + + // TODO(https://github.com/web-platform-tests/wpt/issues/7899): Change to + // some sort of cross-browser GC trigger. + if (self.gc) self.gc(); + + // We are testing that neither of the above two events fire. We assume that a 2 second timeout + // is good enough. We can't use any other API for an end condition because each MessagePort has + // its own independent port message queue, which has no ordering guarantees relative to other + // APIs. + await new Promise(resolve => t.step_timeout(resolve, 2000)); +} + +promise_test(async (t) => { + return detached_frame_test(t, IframeAction.REMOVE_AFTER_CREATION); +}, 'MessageChannel created from a detached iframe should not send messages (remove after create)'); + +promise_test(async (t) => { + return detached_frame_test(t, IframeAction.REMOVE_BEFORE_CREATION); +}, 'MessageChannel created from a detached iframe should not send messages (remove before create)'); \ No newline at end of file diff --git a/src/third_party/blink/web_tests/fast/dom/inert/inert-focus-in-frames.html b/src/third_party/blink/web_tests/fast/dom/inert/inert-focus-in-frames.html index e2b8aa49e23eb..ddeb1a123ae14 --- a/src/third_party/blink/web_tests/fast/dom/inert/inert-focus-in-frames.html +++ b/src/third_party/blink/web_tests/fast/dom/inert/inert-focus-in-frames.html @@ -28,20 +28,20 @@ function frameLoaded() { test(function() { var frame1 = mainIframe.contentWindow.frames[0].document; var target1 = frame1.querySelector('.target'); - testCantFocus(target1); + testCanFocus(target1); var iframe = frame1.querySelector('iframe').contentDocument; - testCantFocus(iframe.querySelector('.target')); - }, "Focus can't go into frames or iframes in inert subtree"); + testCanFocus(iframe.querySelector('.target')); + }, "Focus can go into frames or iframes in inert subtree"); done(); } } -function testCantFocus(element) { +function testCanFocus(element) { focusedElement = null; element.addEventListener('focus', function() { focusedElement = element; }, false); element.focus(); theElement = element; - assert_false(focusedElement === theElement); + assert_equals(focusedElement, theElement); } mainIframe.contentDocument.write(mainIframe.textContent); diff --git a/src/third_party/blink/web_tests/fast/peerconnection/simulcast-munge.html b/src/third_party/blink/web_tests/fast/peerconnection/simulcast-munge.html new file mode 100644 index 0000000000000..2e6dcf92694e4 --- /dev/null +++ b/src/third_party/blink/web_tests/fast/peerconnection/simulcast-munge.html @@ -0,0 +1,50 @@ + + + + Simulcast manipulation + + + + + + \ No newline at end of file diff --git a/src/third_party/blink/web_tests/loader/document-updated-by-xslt-expected.txt b/src/third_party/blink/web_tests/loader/document-updated-by-xslt-expected.txt new file mode 100644 index 0000000000000..a0772fddeab17 --- /dev/null +++ b/src/third_party/blink/web_tests/loader/document-updated-by-xslt-expected.txt @@ -0,0 +1 @@ +PASS if it does not crash in debug. diff --git a/src/third_party/blink/web_tests/loader/document-updated-by-xslt.html b/src/third_party/blink/web_tests/loader/document-updated-by-xslt.html new file mode 100644 index 0000000000000..977ac1467c178 --- /dev/null +++ b/src/third_party/blink/web_tests/loader/document-updated-by-xslt.html @@ -0,0 +1,10 @@ + + + + +

PASS if it does not crash in debug.

+ + diff --git a/src/third_party/blink/web_tests/loader/resources/document-updated-by-xslt.svg b/src/third_party/blink/web_tests/loader/resources/document-updated-by-xslt.svg new file mode 100644 index 0000000000000..af48358c6c9a9 --- /dev/null +++ b/src/third_party/blink/web_tests/loader/resources/document-updated-by-xslt.svg @@ -0,0 +1,9 @@ + + + +]> + + + \ No newline at end of file diff --git a/src/third_party/blink/web_tests/platform/mac/inspector-protocol/layout-fonts/languages-emoji-rare-glyphs-expected.txt b/src/third_party/blink/web_tests/platform/mac/inspector-protocol/layout-fonts/languages-emoji-rare-glyphs-expected.txt index 0915cc6c927de..657f94958d42b --- a/src/third_party/blink/web_tests/platform/mac/inspector-protocol/layout-fonts/languages-emoji-rare-glyphs-expected.txt +++ b/src/third_party/blink/web_tests/platform/mac/inspector-protocol/layout-fonts/languages-emoji-rare-glyphs-expected.txt @@ -25,7 +25,7 @@ 🌱🌲🌳🌴🌵🌷🌸🌹🌺🌻🌼💐🌾🌿🍀🍁🍂🍃🍄🌰☺️😀👪 #emoji: -"Apple Color Emoji" : 24 +"Apple Color Emoji" : 23 𓀀𓀁𓀂𓀃𓀄𓀅𓀆𓀇𓀈𓀉𓀊𓀋𓀌𓀍𓀎𓀏 #egyptian_hieroglyphs: diff --git a/src/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html b/src/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html new file mode 100644 index 0000000000000..5668407d7e1e6 --- /dev/null +++ b/src/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html @@ -0,0 +1,15 @@ + +Test fencedframe size restrictions in opaque ads mode. + + + + + + + + + diff --git a/src/third_party/dawn/third_party/tint/src/intrinsic_table.cc b/src/third_party/dawn/third_party/tint/src/intrinsic_table.cc index 23c9e30adde84..0f0c2a7a27f72 --- a/src/third_party/dawn/third_party/tint/src/intrinsic_table.cc +++ b/src/third_party/dawn/third_party/tint/src/intrinsic_table.cc @@ -973,11 +973,16 @@ const sem::Intrinsic* Impl::Match(sem::IntrinsicType intrinsic_type, constexpr int kScorePerMatchedOpenType = 1; constexpr int kScorePerMatchedOpenNumber = 1; - auto num_parameters = overload.num_parameters; - auto num_arguments = static_cast(args.size()); + uint32_t num_parameters = static_cast(overload.num_parameters); + uint32_t num_arguments = static_cast(args.size()); bool overload_matched = true; + if (static_cast(args.size()) > + static_cast(std::numeric_limits::max())) { + overload_matched = false; // No overload has this number of arguments. + } + if (num_parameters != num_arguments) { match_score += kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) - diff --git a/src/third_party/dawn/third_party/tint/src/intrinsic_table_test.cc b/src/third_party/dawn/third_party/tint/src/intrinsic_table_test.cc index dbb759e99fd54..f974e222aa810 --- a/src/third_party/dawn/third_party/tint/src/intrinsic_table_test.cc +++ b/src/third_party/dawn/third_party/tint/src/intrinsic_table_test.cc @@ -14,6 +14,8 @@ #include "src/intrinsic_table.h" +#include + #include "gmock/gmock.h" #include "src/program_builder.h" #include "src/sem/atomic_type.h" @@ -609,5 +611,13 @@ TEST_F(IntrinsicTableTest, SameOverloadReturnsSameIntrinsicPointer) { EXPECT_NE(b, c); } +TEST_F(IntrinsicTableTest, Err257Arguments) { // crbug.com/1323605 + auto* f32 = create(); + std::vector arg_tys(257, f32); + auto* result = table->Lookup(IntrinsicType::kAbs, std::move(arg_tys), Source{}); + ASSERT_EQ(result, nullptr); + ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call")); +} + } // namespace } // namespace tint diff --git a/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleFormat.ts b/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleFormat.ts index 2116da4e6d1ca..d0a9e1eb0c54f --- a/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleFormat.ts +++ b/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleFormat.ts @@ -196,3 +196,26 @@ export const format = (fmt: string, args: SDK.RemoteObject.RemoteObject[]): { addStringToken(fmt); return {tokens, args: args.slice(argIndex)}; }; + +export const updateStyle = (currentStyle: Map, styleToAdd: string): void => { + const ALLOWED_PROPERTY_PREFIXES = ['background', 'border', 'color', 'font', 'line', 'margin', 'padding', 'text']; + const BLOCKED_URL_SCHEMES = ['chrome', 'resource', 'about', 'app', 'http', 'https', 'ftp', 'file']; + + currentStyle.clear(); + const buffer = document.createElement('span'); + buffer.setAttribute('style', styleToAdd); + for (const property of buffer.style) { + if (!ALLOWED_PROPERTY_PREFIXES.some( + prefix => property.startsWith(prefix) || property.startsWith(`-webkit-${prefix}`))) { + continue; + } + const value = buffer.style.getPropertyValue(property); + if (BLOCKED_URL_SCHEMES.some(scheme => value.includes(scheme + ':'))) { + continue; + } + currentStyle.set(property, { + value, + priority: buffer.style.getPropertyPriority(property), + }); + } +}; diff --git a/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleViewMessage.ts b/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleViewMessage.ts index f46cf48eb1790..c3895a55d2de9 --- a/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleViewMessage.ts +++ b/src/third_party/devtools-frontend/src/front_end/panels/console/ConsoleViewMessage.ts @@ -53,7 +53,7 @@ import * as UI from '../../ui/legacy/legacy.js'; import objectValueStyles from '../../ui/legacy/components/object_ui/objectValue.css.js'; import type {Chrome} from '../../../extension-api/ExtensionAPI.js'; // eslint-disable-line rulesdir/es_modules_import -import {format} from './ConsoleFormat.js'; +import {format, updateStyle} from './ConsoleFormat.js'; import type {ConsoleViewportElement} from './ConsoleViewport.js'; import consoleViewStyles from './consoleView.css.js'; import {parseSourcePositionsFromErrorStack} from './ErrorStackParser.js'; @@ -903,26 +903,10 @@ export class ConsoleViewMessage implements ConsoleViewportElement { } break; } - case 'style': { + case 'style': // Make sure that allowed properties do not interfere with link visibility. - const ALLOWED_PROPERTY_PREFIXES = - ['background', 'border', 'color', 'font', 'line', 'margin', 'padding', 'text']; - - currentStyle.clear(); - const buffer = document.createElement('span'); - buffer.setAttribute('style', token.value); - for (const property of buffer.style) { - if (!ALLOWED_PROPERTY_PREFIXES.some( - prefix => property.startsWith(prefix) || property.startsWith(`-webkit-${prefix}`))) { - continue; - } - currentStyle.set(property, { - value: buffer.style.getPropertyValue(property), - priority: buffer.style.getPropertyPriority(property), - }); - } + updateStyle(currentStyle, token.value); break; - } } } return args; diff --git a/src/third_party/devtools-frontend/src/test/e2e/console/console-log_test.ts b/src/third_party/devtools-frontend/src/test/e2e/console/console-log_test.ts index c0d4eb587d178..c299e3a512e7e --- a/src/third_party/devtools-frontend/src/test/e2e/console/console-log_test.ts +++ b/src/third_party/devtools-frontend/src/test/e2e/console/console-log_test.ts @@ -3,10 +3,11 @@ // found in the LICENSE file. import {assert} from 'chai'; +import type * as puppeteer from 'puppeteer'; import {activeElement, activeElementAccessibleName, activeElementTextContent, getBrowserAndPages, tabBackward, tabForward, waitForFunction} from '../../shared/helper.js'; import {describe, it} from '../../shared/mocha-extensions.js'; -import {focusConsolePrompt, getConsoleMessages, getStructuredConsoleMessages, navigateToConsoleTab, showVerboseMessages, waitForLastConsoleMessageToHaveContent} from '../helpers/console-helpers.js'; +import {CONSOLE_FIRST_MESSAGES_SELECTOR, focusConsolePrompt, getConsoleMessages, getCurrentConsoleMessages, getStructuredConsoleMessages, navigateToConsoleTab, showVerboseMessages, waitForLastConsoleMessageToHaveContent} from '../helpers/console-helpers.js'; /* eslint-disable no-console */ @@ -301,4 +302,87 @@ describe('The Console Tab', async () => { assert.strictEqual(await activeElementAccessibleName(), 'Console prompt'); }); }); + + describe('Console log message formatters', () => { + async function getConsoleMessageTextChunksWithStyle( + frontend: puppeteer.Page, styles: string[] = []): Promise { + return await frontend.evaluate((selector, styles: string[]) => { + return [...document.querySelectorAll(selector)].map(message => [...message.childNodes].map(node => { + // For all nodes, extract text. + const result = [node.textContent]; + // For element nodes, get the requested styles. + for (const style of styles) { + result.push(node.style?.[style] ?? ''); + } + return result; + })); + }, CONSOLE_FIRST_MESSAGES_SELECTOR, styles); + } + + async function waitForConsoleMessages(count: number): Promise { + await waitForFunction(async () => { + const messages = await getCurrentConsoleMessages(); + return messages.length === count ? messages : null; + }); + } + + it('expand primitive formatters', async () => { + const {frontend, target} = getBrowserAndPages(); + await navigateToConsoleTab(); + await target.evaluate(() => { + console.log('--%s--', 'text'); + console.log('--%s--', '%s%i', 'u', 2); + console.log('Number %i', 42); + console.log('Float %f', 1.5); + }); + + await waitForConsoleMessages(4); + const texts = await getConsoleMessageTextChunksWithStyle(frontend); + assert.deepEqual(texts, [[['--text--']], [['--u2--']], [['Number 42']], [['Float 1.5']]]); + }); + + it('expand %c formatter with color style', async () => { + const {frontend, target} = getBrowserAndPages(); + await navigateToConsoleTab(); + await target.evaluate(() => console.log('PRE%cRED%cBLUE', 'color:red', 'color:blue')); + + await waitForConsoleMessages(1); + + // Extract the text and color. + const textsAndStyles = await getConsoleMessageTextChunksWithStyle(frontend, ['color']); + assert.deepEqual(textsAndStyles, [[['PRE', ''], ['RED', 'red'], ['BLUE', 'blue']]]); + }); + + it('expand %c formatter with background image in data URL', async () => { + const {frontend, target} = getBrowserAndPages(); + await navigateToConsoleTab(); + await target.evaluate( + () => console.log( + 'PRE%cBG', + 'background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAAAAABzHgM7AAAAF0lEQVR42mM4Awb/wYCBYg6EgghRzAEAWDWBGQVyKPMAAAAASUVORK5CYII=);')); + + await waitForConsoleMessages(1); + + // Check that the 'BG' text has the background image set. + const textsAndStyles = await getConsoleMessageTextChunksWithStyle(frontend, ['background-image']); + assert.strictEqual(textsAndStyles.length, 1); + const message = textsAndStyles[0]; + assert.strictEqual(message.length, 2); + const textWithBackground = message[1]; + assert.strictEqual(textWithBackground[0], 'BG'); + assert.include(textWithBackground[1], 'data:image/png;base64'); + }); + + it('filter out %c formatter if background image is remote URL', async () => { + const {frontend, target} = getBrowserAndPages(); + await navigateToConsoleTab(); + await target.evaluate(() => console.log('PRE%cBG', 'background-image: url(http://localhost/image.png)')); + + await waitForConsoleMessages(1); + + // Check that the 'BG' text has no bakcground image. + const textsAndStyles = await getConsoleMessageTextChunksWithStyle(frontend, ['background-image']); + assert.deepEqual(textsAndStyles, [[['PRE', ''], ['BG', '']]]); + }); + }); }); diff --git a/src/third_party/devtools-frontend/src/test/unittests/front_end/panels/console/ConsoleFormat_test.ts b/src/third_party/devtools-frontend/src/test/unittests/front_end/panels/console/ConsoleFormat_test.ts index c74e8d7e7eff9..fc14565c99738 --- a/src/third_party/devtools-frontend/src/test/unittests/front_end/panels/console/ConsoleFormat_test.ts +++ b/src/third_party/devtools-frontend/src/test/unittests/front_end/panels/console/ConsoleFormat_test.ts @@ -326,4 +326,104 @@ describe('ConsoleFormat', () => { ]); }); }); + + describe('updateStyle', () => { + it('allows allow-listed styles', () => { + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, 'border-top-style:solid'); + assert.deepEqual(styles.get('border-top-style'), {value: 'solid', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'color:red'); + assert.deepEqual(styles.get('color'), {value: 'red', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'font-family:serif'); + assert.deepEqual(styles.get('font-family'), {value: 'serif', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'line-height:100%'); + assert.deepEqual(styles.get('line-height'), {value: '100%', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'margin-top:30px'); + assert.deepEqual(styles.get('margin-top'), {value: '30px', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'padding-top : 20px'); + assert.deepEqual(styles.get('padding-top'), {value: '20px', priority: ''}); + + Console.ConsoleFormat.updateStyle(styles, 'text-align : center'); + assert.deepEqual(styles.get('text-align'), {value: 'center', priority: ''}); + }); + + it('handles multiple styles', () => { + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, 'font-size:14px; color:red'); + assert.deepEqual(styles.get('color'), {value: 'red', priority: ''}); + assert.deepEqual(styles.get('font-size'), {value: '14px', priority: ''}); + }); + + it('resets styles', () => { + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, 'font-size:14px; color:red'); + Console.ConsoleFormat.updateStyle(styles, 'color:red'); + assert.isFalse(styles.has('font-size')); + }); + + it('blocks styles outside of allow-list', () => { + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, 'visibility:hidden'); + assert.isFalse(styles.has('visibility')); + + Console.ConsoleFormat.updateStyle(styles, 'width:100px'); + assert.isFalse(styles.has('width')); + + Console.ConsoleFormat.updateStyle(styles, 'box-sizing:border-box'); + assert.isFalse(styles.has('box-sizing')); + }); + + it('blocks block-listed url schemes in values', () => { + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(http://localhost/a.png)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(https://localhost/a.png)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(resource://localhost/a.png)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(app://com.foo.bar/index.html)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(chrome://a/b.png)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(about:flags)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(ftp://localhost/a.png)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'background-image:url(file://c/a.txt)'); + assert.isFalse(styles.has('background-image')); + + Console.ConsoleFormat.updateStyle(styles, 'border-image-source:url(file://c/a.txt)'); + assert.isFalse(styles.has('border-image-source')); + }); + + it('allows data urls in values', () => { + const dataUrl = + 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAAAAABzHgM7AAAAF0lEQVR42mM4Awb/wYCBYg6EgghRzAEAWDWBGQVyKPMAAAAASUVORK5CYII=)'; + + const styles = new Map(); + + Console.ConsoleFormat.updateStyle(styles, `background-image:${dataUrl}`); + assert.include(styles.get('background-image').value, 'data:image/png;base64'); + + Console.ConsoleFormat.updateStyle(styles, `border-image-source:${dataUrl}`); + assert.include(styles.get('border-image-source').value, 'data:image/png;base64'); + }); + }); }); diff --git a/src/third_party/freetype/BUILD.gn b/src/third_party/freetype/BUILD.gn index 7769b514cab1a..82a57d7dad85a --- a/src/third_party/freetype/BUILD.gn +++ b/src/third_party/freetype/BUILD.gn @@ -132,7 +132,7 @@ source_set("freetype_source") { ] } - if (is_linux || is_chromeos || is_chromecast) { + if (is_linux || is_chromeos || (is_chromecast && is_android)) { # Needed for content_shell on Linux and Chromecast, since fontconfig # requires FT_Get_BDF_Property. sources += [ "src/src/base/ftbdf.c" ] diff --git a/src/third_party/freetype/README.chromium b/src/third_party/freetype/README.chromium index 0b472996edd7c..e6d95207a85bf --- a/src/third_party/freetype/README.chromium +++ b/src/third_party/freetype/README.chromium @@ -1,7 +1,7 @@ Name: FreeType URL: http://www.freetype.org/ -Version: VER-2-11-1-112 -Revision: 034e5dbf92ea3a7ea7c9322e47a3a50ff23f7b55 +Version: VER-2-12-1-41-gbec4ef415 +Revision: bec4ef415ef07ad1fa9542978136d9863dd7a6d0 CPEPrefix: cpe:/a:freetype:freetype:2.11.1 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent JPEG Group) licenses" diff --git a/src/third_party/freetype/src/.gitlab-ci.yml b/src/third_party/freetype/src/.gitlab-ci.yml index f3af842beaca2..a6f75d61c5752 --- a/src/third_party/freetype/src/.gitlab-ci.yml +++ b/src/third_party/freetype/src/.gitlab-ci.yml @@ -5,7 +5,7 @@ stages: # FIXME: Use --werror once warnings are fixed. variables: - MESON_ARGS: --fatal-meson-warnings + MESON_ARGS: --fatal-meson-warnings --default-library=both MESON_ARGS_WINDOWS: ${MESON_ARGS} --force-fallback-for=zlib .build windows common: @@ -47,8 +47,18 @@ variables: - Import-Certificate -CertStoreLocation "Cert:\LocalMachine\Root" "C:\roots.sst" # Make sure meson is up to date so we don't need to rebuild the image # with each release. - - pip3 install meson==0.59.1 + - pip3 install -U 'meson==0.59.*' + - pip3 install --upgrade certifi - pip3 install -U ninja + + # Generate a UWP cross-file in case it's used + - $PSDefaultParameterValues['Out-File:Encoding'] = 'ASCII' + - echo "[binaries]" > uwp-crossfile.meson + - echo "c = 'cl'" >> uwp-crossfile.meson + - echo "strip = ['true']" >> uwp-crossfile.meson + - echo "[built-in options]" >> uwp-crossfile.meson + - echo "c_args = ['-DWINAPI_FAMILY=WINAPI_FAMILY_APP', '-DUNICODE', '-D_WIN32_WINNT=0x0A00', '-we4013']" >> uwp-crossfile.meson + - echo "c_winlibs = ['windowsapp.lib']" >> uwp-crossfile.meson script: # For some reason, options are separated by newlines instead of spaces, # so we have to replace them first. @@ -59,11 +69,10 @@ variables: # script. Environment variables substitutions is done by PowerShell # before calling `cmd.exe`, that's why we use `$env:FOO` instead of # `%FOO%`. - - cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=$env:ARCH && - meson setup build $env:MESON_ARGS_WINDOWS && - meson compile --verbose -C build && - meson test -C build && - meson test -C build --benchmark" + - cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=$env:ARCH $env:VS_UWP && + meson setup build $env:MESON_ARGS_WINDOWS $env:MESON_ARGS_UWP && + meson compile --verbose -C build + $env:MESON_WINDOWS_TESTS" # Format of job names: @@ -76,12 +85,20 @@ windows meson vs2017 amd64: extends: '.build windows meson' variables: ARCH: 'amd64' + MESON_WINDOWS_TESTS: '&& meson test -C build && meson test -C build --benchmark' windows meson vs2017 x86: extends: '.build windows meson' variables: ARCH: 'x86' + MESON_WINDOWS_TESTS: '&& meson test -C build && meson test -C build --benchmark' +windows meson vs2017 amd64 uwp: + extends: '.build windows meson' + variables: + ARCH: 'amd64' + VS_UWP: '-app_platform=UWP' + MESON_ARGS_UWP: '--cross-file uwp-crossfile.meson -Dc_winlibs="windowsapp.lib"' # Linux Jobs. # @@ -130,7 +147,8 @@ linux autotools libs clang: linux meson: extends: '.build linux common' script: | - meson setup build -Dbrotli=disabled \ + meson setup build ${MESON_ARGS} \ + -Dbrotli=disabled \ -Dbzip2=disabled \ -Dharfbuzz=disabled \ -Dpng=disabled \ @@ -142,7 +160,8 @@ linux meson: linux meson libs: extends: '.build linux common' script: | - meson setup build -Dbrotli=enabled \ + meson setup build ${MESON_ARGS} \ + -Dbrotli=enabled \ -Dbzip2=enabled \ -Dharfbuzz=disabled \ -Dpng=disabled \ @@ -205,7 +224,7 @@ macos meson: - pip3 install -U meson - pip3 install --upgrade certifi - pip3 install -U ninja - - - meson setup build + + - meson setup build ${MESON_ARGS} - meson compile --verbose -C build - sudo meson install -C build diff --git a/src/third_party/freetype/src/CMakeLists.txt b/src/third_party/freetype/src/CMakeLists.txt index 9e66cde670dc9..d12897cba9380 --- a/src/third_party/freetype/src/CMakeLists.txt +++ b/src/third_party/freetype/src/CMakeLists.txt @@ -121,7 +121,6 @@ endif () include(CheckIncludeFile) include(CMakeDependentOption) -include(FindPkgConfig) # CMAKE_TOOLCHAIN_FILE must be set before `project' is called, which # configures the base build environment and references the toolchain file @@ -162,7 +161,7 @@ endif () project(freetype C) set(VERSION_MAJOR "2") -set(VERSION_MINOR "11") +set(VERSION_MINOR "12") set(VERSION_PATCH "1") # Generate LIBRARY_VERSION and LIBRARY_SOVERSION. @@ -245,6 +244,8 @@ endif () # Find dependencies +include(FindPkgConfig) + if (NOT FT_DISABLE_HARFBUZZ) set(HARFBUZZ_MIN_VERSION "2.0.0") if (FT_REQUIRE_HARFBUZZ) @@ -491,46 +492,48 @@ if (BUILD_FRAMEWORK) ) set_target_properties(freetype PROPERTIES FRAMEWORK TRUE - MACOSX_FRAMEWORK_INFO_PLIST builds/mac/freetype-Info.plist + MACOSX_FRAMEWORK_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/builds/mac/freetype-Info.plist PUBLIC_HEADER "${PUBLIC_HEADERS}" XCODE_ATTRIBUTE_INSTALL_PATH "@rpath" ) endif () -set(PKG_CONFIG_REQUIRED_PRIVATE "") -set(PKG_CONFIG_LIBS_PRIVATE "") +set(PKGCONFIG_REQUIRES "") +set(PKGCONFIG_REQUIRES_PRIVATE "") +set(PKGCONFIG_LIBS "-L\${libdir} -lfreetype") +set(PKGCONFIG_LIBS_PRIVATE "") if (ZLIB_FOUND) target_link_libraries(freetype PRIVATE ${ZLIB_LIBRARIES}) target_include_directories(freetype PRIVATE ${ZLIB_INCLUDE_DIRS}) - list(APPEND PKG_CONFIG_REQUIRED_PRIVATE "zlib") + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "zlib") endif () if (BZIP2_FOUND) target_link_libraries(freetype PRIVATE ${BZIP2_LIBRARIES}) target_include_directories(freetype PRIVATE ${BZIP2_INCLUDE_DIR}) # not BZIP2_INCLUDE_DIRS if (PC_BZIP2_FOUND) - list(APPEND PKG_CONFIG_REQUIRED_PRIVATE "bzip2") + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "bzip2") else () - list(APPEND PKG_CONFIG_LIBS_PRIVATE "-lbz2") + list(APPEND PKGCONFIG_LIBS_PRIVATE "-lbz2") endif () endif () if (PNG_FOUND) target_link_libraries(freetype PRIVATE ${PNG_LIBRARIES}) target_compile_definitions(freetype PRIVATE ${PNG_DEFINITIONS}) target_include_directories(freetype PRIVATE ${PNG_INCLUDE_DIRS}) - list(APPEND PKG_CONFIG_REQUIRED_PRIVATE "libpng") + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "libpng") endif () if (HarfBuzz_FOUND) target_link_libraries(freetype PRIVATE ${HarfBuzz_LIBRARY}) target_include_directories(freetype PRIVATE ${HarfBuzz_INCLUDE_DIRS}) - list(APPEND PKG_CONFIG_REQUIRED_PRIVATE "harfbuzz >= ${HARFBUZZ_MIN_VERSION}") + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "harfbuzz >= ${HARFBUZZ_MIN_VERSION}") endif () if (BROTLIDEC_FOUND) target_link_libraries(freetype PRIVATE ${BROTLIDEC_LIBRARIES}) target_compile_definitions(freetype PRIVATE ${BROTLIDEC_DEFINITIONS}) target_include_directories(freetype PRIVATE ${BROTLIDEC_INCLUDE_DIRS}) - list(APPEND PKG_CONFIG_REQUIRED_PRIVATE "libbrotlidec") + list(APPEND PKGCONFIG_REQUIRES_PRIVATE "libbrotlidec") endif () @@ -557,7 +560,7 @@ if (NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL) # Generate the pkg-config file file(READ "${PROJECT_SOURCE_DIR}/builds/unix/freetype2.in" FREETYPE2_PC_IN) - string(REPLACE ";" ", " PKG_CONFIG_REQUIRED_PRIVATE "${PKG_CONFIG_REQUIRED_PRIVATE}") + string(REPLACE ";" ", " PKGCONFIG_REQUIRES_PRIVATE "${PKGCONFIG_REQUIRES_PRIVATE}") string(REPLACE "%prefix%" ${CMAKE_INSTALL_PREFIX} FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) @@ -569,10 +572,26 @@ if (NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL) FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) string(REPLACE "%ft_version%" "${LIBTOOL_CURRENT}.${LIBTOOL_REVISION}.${LIBTOOL_AGE}" FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) - string(REPLACE "%REQUIRES_PRIVATE%" "${PKG_CONFIG_REQUIRED_PRIVATE}" - FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) - string(REPLACE "%LIBS_PRIVATE%" "${PKG_CONFIG_LIBS_PRIVATE}" - FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + + if (BUILD_SHARED_LIBS) + string(REPLACE "%PKGCONFIG_REQUIRES%" "${PKGCONFIG_REQUIRES}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_REQUIRES_PRIVATE%" "${PKGCONFIG_REQUIRES_PRIVATE}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_LIBS%" "${PKGCONFIG_LIBS}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_LIBS_PRIVATE%" "${PKGCONFIG_LIBS_PRIVATE}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + else () + string(REPLACE "%PKGCONFIG_REQUIRES%" "${PKGCONFIG_REQUIRES} ${PKGCONFIG_REQUIRES_PRIVATE}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_REQUIRES_PRIVATE%" "" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_LIBS%" "${PKGCONFIG_LIBS} ${PKGCONFIG_LIBS_PRIVATE}" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + string(REPLACE "%PKGCONFIG_LIBS_PRIVATE%" "" + FREETYPE2_PC_IN ${FREETYPE2_PC_IN}) + endif () set(FREETYPE2_PC_IN_NAME "${PROJECT_BINARY_DIR}/freetype2.pc") if (EXISTS "${FREETYPE2_PC_IN_NAME}") diff --git a/src/third_party/freetype/src/README b/src/third_party/freetype/src/README index da01bfed6eef6..85601070752a3 --- a/src/third_party/freetype/src/README +++ b/src/third_party/freetype/src/README @@ -1,4 +1,4 @@ -FreeType 2.11.1 +FreeType 2.12.1 =============== Homepage: https://www.freetype.org @@ -16,7 +16,9 @@ Read the files `docs/INSTALL*` for installation instructions; see the file `docs/LICENSE.TXT` for the available licenses. For using FreeType's git repository instead of a distribution bundle, -please read file `README.git`. +please read file `README.git`. Note that you have to actually clone +the repository; using a snapshot will not work (in other words, don't +use gitlab's 'Download' button). The FreeType 2 API reference is located in directory `docs/reference`; use the file `index.html` as the top entry point. [Please note that @@ -30,9 +32,9 @@ sites. Go to and download one of the following files. - freetype-doc-2.11.1.tar.xz - freetype-doc-2.11.1.tar.gz - ftdoc2111.zip + freetype-doc-2.12.1.tar.xz + freetype-doc-2.12.1.tar.gz + ftdoc2121.zip To view the documentation online, go to diff --git a/src/third_party/freetype/src/autogen.sh b/src/third_party/freetype/src/autogen.sh index 1545c63ad5c2c..b5fc1beb5e774 --- a/src/third_party/freetype/src/autogen.sh +++ b/src/third_party/freetype/src/autogen.sh @@ -182,7 +182,7 @@ copy_submodule_files () cp $DLG_SRC_DIR/* src/dlg } -if test -d ".git"; then +if test -e ".git"; then DLG_INC_DIR=subprojects/dlg/include/dlg DLG_SRC_DIR=subprojects/dlg/src/dlg diff --git a/src/third_party/freetype/src/builds/unix/configure.raw b/src/third_party/freetype/src/builds/unix/configure.raw index f84dcecc99157..fb943fd2a79a9 --- a/src/third_party/freetype/src/builds/unix/configure.raw +++ b/src/third_party/freetype/src/builds/unix/configure.raw @@ -17,7 +17,7 @@ AC_CONFIG_SRCDIR([ftconfig.h.in]) # Don't forget to update `docs/VERSIONS.TXT'! -version_info='24:1:18' +version_info='24:3:18' AC_SUBST([version_info]) ft_version=`echo $version_info | tr : .` AC_SUBST([ft_version]) @@ -530,16 +530,28 @@ AC_SEARCH_LIBS([clock_gettime], [test "$ac_cv_search_clock_gettime" = "none required" \ || LIB_CLOCK_GETTIME=$ac_cv_search_clock_gettime]) -# 'librsvg' is needed to demonstrate SVG support. -PKG_CHECK_MODULES([LIBRSVG], [librsvg-2.0 >= 2.46.0], - [have_librsvg="yes (pkg-config)"], [have_librsvg=no]) - FT_DEMO_CFLAGS="" FT_DEMO_LDFLAGS="$LIB_CLOCK_GETTIME" -if test "$have_librsvg" != no; then - FT_DEMO_CFLAGS="$FT_DEMO_CFLAGS $LIBRSVG_CFLAGS -DHAVE_LIBRSVG" - FT_DEMO_LDFLAGS="$FT_DEMO_LDFLAGS $LIBRSVG_LIBS" +# 'librsvg' is needed to demonstrate SVG support. +AC_ARG_WITH([librsvg], + [AS_HELP_STRING([--with-librsvg=@<:@yes|no|auto@:>@], + [support OpenType SVG fonts in FreeType demo programs @<:@default=auto@:>@])], + [], [with_librsvg=auto]) + +have_librsvg=no +if test x"$with_librsvg" = xyes -o x"$with_librsvg" = xauto; then + PKG_CHECK_MODULES([LIBRSVG], [librsvg-2.0 >= 2.46.0], + [have_librsvg="yes (pkg-config)"], [:]) + + if test "$have_librsvg" != no; then + FT_DEMO_CFLAGS="$FT_DEMO_CFLAGS $LIBRSVG_CFLAGS -DHAVE_LIBRSVG" + FT_DEMO_LDFLAGS="$FT_DEMO_LDFLAGS $LIBRSVG_LIBS" + fi +fi + +if test x"$with_librsvg" = xyes -a "$have_librsvg" = no; then + AC_MSG_ERROR([librsvg support requested but library not found]) fi AC_SUBST([FT_DEMO_CFLAGS]) @@ -985,32 +997,32 @@ fi # entries in Requires.private are separated by commas -REQUIRES_PRIVATE="$zlib_reqpriv, \ - $bzip2_reqpriv, \ - $libpng_reqpriv, \ - $harfbuzz_reqpriv, \ - $brotli_reqpriv" +PKGCONFIG_REQUIRES_PRIVATE="$zlib_reqpriv, \ + $bzip2_reqpriv, \ + $libpng_reqpriv, \ + $harfbuzz_reqpriv, \ + $brotli_reqpriv" # beautify -REQUIRES_PRIVATE=`echo "$REQUIRES_PRIVATE" \ - | sed -e 's/^ *//' \ - -e 's/ *$//' \ - -e 's/, */,/g' \ - -e 's/,,*/,/g' \ - -e 's/^,*//' \ - -e 's/,*$//' \ - -e 's/,/, /g'` - -LIBS_PRIVATE="$zlib_libspriv \ - $bzip2_libspriv \ - $libpng_libspriv \ - $harfbuzz_libspriv \ - $brotli_libspriv \ - $ft2_extra_libs" +PKGCONFIG_REQUIRES_PRIVATE=`echo "$PKGCONFIG_REQUIRES_PRIVATE" \ + | sed -e 's/^ *//' \ + -e 's/ *$//' \ + -e 's/, */,/g' \ + -e 's/,,*/,/g' \ + -e 's/^,*//' \ + -e 's/,*$//' \ + -e 's/,/, /g'` + +PKGCONFIG_LIBS_PRIVATE="$zlib_libspriv \ + $bzip2_libspriv \ + $libpng_libspriv \ + $harfbuzz_libspriv \ + $brotli_libspriv \ + $ft2_extra_libs" # beautify -LIBS_PRIVATE=`echo "$LIBS_PRIVATE" \ - | sed -e 's/^ *//' \ - -e 's/ *$//' \ - -e 's/ */ /g'` +PKGCONFIG_LIBS_PRIVATE=`echo "$PKGCONFIG_LIBS_PRIVATE" \ + | sed -e 's/^ *//' \ + -e 's/ *$//' \ + -e 's/ */ /g'` LIBSSTATIC_CONFIG="-lfreetype \ $zlib_libsstaticconf \ @@ -1028,10 +1040,28 @@ LIBSSTATIC_CONFIG=`echo "$LIBSSTATIC_CONFIG" \ -e 's/ *$//' \ -e 's/ */ /g'` +# If FreeType gets installed with `--disable-shared', don't use +# 'private' fields. `pkg-config' only looks into `.pc' files and is +# completely agnostic to whether shared libraries are actually present +# or not. As a consequence, the user had to specify `--static' while +# calling `pkg-config', which configure scripts are normally not +# prepared for. + +PKGCONFIG_REQUIRES= +PKGCONFIG_LIBS='-L${libdir} -lfreetype' + +if test $enable_shared = "no"; then + PKGCONFIG_REQUIRES="$PKGCONFIG_REQUIRES $PKGCONFIG_REQUIRES_PRIVATE" + PKGCONFIG_REQUIRES_PRIVATE= + PKGCONFIG_LIBS="$PKGCONFIG_LIBS $PKGCONFIG_LIBS_PRIVATE" + PKGCONFIG_LIBS_PRIVATE= +fi AC_SUBST([ftmac_c]) -AC_SUBST([REQUIRES_PRIVATE]) -AC_SUBST([LIBS_PRIVATE]) +AC_SUBST([PKGCONFIG_REQUIRES]) +AC_SUBST([PKGCONFIG_LIBS]) +AC_SUBST([PKGCONFIG_REQUIRES_PRIVATE]) +AC_SUBST([PKGCONFIG_LIBS_PRIVATE]) AC_SUBST([LIBSSTATIC_CONFIG]) AC_SUBST([hardcode_libdir_flag_spec]) diff --git a/src/third_party/freetype/src/builds/unix/freetype2.in b/src/third_party/freetype/src/builds/unix/freetype2.in index 2d759ecf8bfdd..fe389f4b6fef5 --- a/src/third_party/freetype/src/builds/unix/freetype2.in +++ b/src/third_party/freetype/src/builds/unix/freetype2.in @@ -7,8 +7,8 @@ Name: FreeType 2 URL: https://freetype.org Description: A free, high-quality, and portable font engine. Version: %ft_version% -Requires: -Requires.private: %REQUIRES_PRIVATE% -Libs: -L${libdir} -lfreetype -Libs.private: %LIBS_PRIVATE% +Requires: %PKGCONFIG_REQUIRES% +Requires.private: %PKGCONFIG_REQUIRES_PRIVATE% +Libs: %PKGCONFIG_LIBS% +Libs.private: %PKGCONFIG_LIBS_PRIVATE% Cflags: -I${includedir}/freetype2 diff --git a/src/third_party/freetype/src/builds/unix/unix-def.in b/src/third_party/freetype/src/builds/unix/unix-def.in index 1ed6e9af27fb8..8e298ac591079 --- a/src/third_party/freetype/src/builds/unix/unix-def.in +++ b/src/third_party/freetype/src/builds/unix/unix-def.in @@ -68,12 +68,14 @@ version_info := @version_info@ # Variables needed for `freetype-config' and `freetype.pc'. # -PKG_CONFIG := @PKG_CONFIG@ -REQUIRES_PRIVATE := @REQUIRES_PRIVATE@ -LIBS_PRIVATE := @LIBS_PRIVATE@ -LIBSSTATIC_CONFIG := @LIBSSTATIC_CONFIG@ -build_libtool_libs := @build_libtool_libs@ -ft_version := @ft_version@ +PKG_CONFIG := @PKG_CONFIG@ +PKGCONFIG_REQUIRES := @PKGCONFIG_REQUIRES@ +PKGCONFIG_REQUIRES_PRIVATE := @PKGCONFIG_REQUIRES_PRIVATE@ +PKGCONFIG_LIBS := @PKGCONFIG_LIBS@ +PKGCONFIG_LIBS_PRIVATE := @PKGCONFIG_LIBS_PRIVATE@ +LIBSSTATIC_CONFIG := @LIBSSTATIC_CONFIG@ +build_libtool_libs := @build_libtool_libs@ +ft_version := @ft_version@ # The directory where all library files are placed. # @@ -137,15 +139,17 @@ prefix_x := $(subst $(space),\\$(space),$(prefix)) $(OBJ_BUILD)/freetype2.pc: $(TOP_DIR)/builds/unix/freetype2.in rm -f $@ $@.tmp - sed -e 's|%REQUIRES_PRIVATE%|$(REQUIRES_PRIVATE)|' \ - -e 's|%LIBS_PRIVATE%|$(LIBS_PRIVATE)|' \ - -e 's|%build_libtool_libs%|$(build_libtool_libs)|' \ - -e 's|%exec_prefix%|$(exec_prefix_x)|' \ - -e 's|%ft_version%|$(ft_version)|' \ - -e 's|%includedir%|$(includedir_x)|' \ - -e 's|%libdir%|$(libdir_x)|' \ - -e 's|%prefix%|$(prefix_x)|' \ - $< \ + sed -e 's|%PKGCONFIG_REQUIRES%|$(PKGCONFIG_REQUIRES)|' \ + -e 's|%PKGCONFIG_REQUIRES_PRIVATE%|$(PKGCONFIG_REQUIRES_PRIVATE)|' \ + -e 's|%PKGCONFIG_LIBS%|$(PKGCONFIG_LIBS)|' \ + -e 's|%PKGCONFIG_LIBS_PRIVATE%|$(PKGCONFIG_LIBS_PRIVATE)|' \ + -e 's|%build_libtool_libs%|$(build_libtool_libs)|' \ + -e 's|%exec_prefix%|$(exec_prefix_x)|' \ + -e 's|%ft_version%|$(ft_version)|' \ + -e 's|%includedir%|$(includedir_x)|' \ + -e 's|%libdir%|$(libdir_x)|' \ + -e 's|%prefix%|$(prefix_x)|' \ + $< \ > $@.tmp chmod a-w $@.tmp mv $@.tmp $@ diff --git a/src/third_party/freetype/src/builds/wince/vc2005-ce/index.html b/src/third_party/freetype/src/builds/wince/vc2005-ce/index.html index 3e42cf99253fd..0b711ff90b698 --- a/src/third_party/freetype/src/builds/wince/vc2005-ce/index.html +++ b/src/third_party/freetype/src/builds/wince/vc2005-ce/index.html @@ -21,7 +21,7 @@ the following targets:
  • PPC/SP WM6 (Windows Mobile 6)
  • -It compiles the following libraries from the FreeType 2.11.1 sources:

    +It compiles the following libraries from the FreeType 2.12.1 sources:

      diff --git a/src/third_party/freetype/src/builds/wince/vc2008-ce/index.html b/src/third_party/freetype/src/builds/wince/vc2008-ce/index.html
      index 645675c721a11..23f06ba0300b7
      --- a/src/third_party/freetype/src/builds/wince/vc2008-ce/index.html
      +++ b/src/third_party/freetype/src/builds/wince/vc2008-ce/index.html
      @@ -21,7 +21,7 @@ the following targets:
         
    • PPC/SP WM6 (Windows Mobile 6)
    -It compiles the following libraries from the FreeType 2.11.1 sources:

    +It compiles the following libraries from the FreeType 2.12.1 sources: